From dfa89c178edb50535e99b5081fd2a3a262c53d61 Mon Sep 17 00:00:00 2001 From: Michael Vandeberg Date: Thu, 27 Aug 2026 18:00:22 -0600 Subject: [PATCH] docs: compile the reference examples and inject them Nothing compiled the `@code` examples in the public headers, so broken examples reached readers. socket_option.hpp is typical. All 19 of its examples call set_option on a `sock` that no code declares. Each example is now a tagged region in a file under test/doc/reference/. The file name comes from the symbol that the example documents. boost_corosio_doc_tests compiles these files. It uses the same warnings-as-errors settings as the page snippets. A Lua extension in doc/addons/extensions/ injects each region into the MrDocs corpus when the docs build runs. The reference then shows code that the build checked. The two halves are independent. CI compiles the files even if the docs build does not run. The docs build injects the regions even if they do not compile. A `@par !example ` marker in the docstring gives the position of the example and the name of the region. The transform replaces the marker, so each example keeps its place and the page gets no new heading. A named region keeps two overloads in one file independent of each other. Corosio also needs an ownership filter. Corosio depends on Capy, and MrDocs extracts Capy symbols into the corosio corpus. Capy headers hold 98 markers of their own, and the fail-closed guard reads them as examples that corosio cannot supply. `owned()` skips a symbol in three conditions. Its extraction is `dependency`. Its docstring is a copy from a base class outside the corpus. Or its qualified name is not a corosio name. The second condition is necessary, because io_context::service has a corosio name and a Capy docstring. MrDocs loads extensions only from `/share/mrdocs/addons/extensions`, and its `addons-supplemental` key has no effect. Therefore doc/build_antora.sh installs the extension into a MrDocs, exports MRDOCS_ROOT, and writes it to GITHUB_ENV. This also moves the docs to the MrDocs develop channel, which the extension API needs. `generate:` becomes `generator:`. `inaccessible-members` and `inaccessible-bases` are gone, and the extract-private* defaults replace them. A diff against a pre-change baseline shows no other change. The docs workflow searches the HTML for an example that only a reference file contains. This check is necessary, because a MrDocs without the extension renders no examples and still reports success. The paths filter now includes include/ and test/doc/reference/. A coverage build skips the examples. No test runs them, and each one emits header template instantiations that no code calls. The CMake guard does this work, because every corosio coverage leg uses CMake. test/doc/Jamfile holds a parallel guard for a possible b2 coverage leg. test/doc/CMakeLists.txt now has the warnings-as-errors settings that the Jamfile always had. It also links boost_corosio_openssl where CMake finds OpenSSL. The openssl_stream constructor template calls a non-inline member, and g++ leaves that member undefined at -O0. Compilation alone cannot find some faults, and this change corrects them. Three examples could never compile. The tcp_server restart sequence omitted ioc.restart(), and join() then blocked forever. The certificate-pinning example compared each chain certificate to one DER, so every handshake failed. Two examples held a passphrase in the source. The CRL examples set a revocation policy but left verification off. Both TLS stream examples moved a socket into owning mode while a reference-mode stream pointed at it. Nine comments stated behaviour that the library does not have. One header change is not documentation. The MSVC-4251 pragma in tls_context.hpp moves above the class docstring. MrDocs dropped that docstring while the preprocessor block was between the comment and the declaration. 19 `@code` blocks stay in the headers. 14 are on detail symbols that MrDocs does not publish. 5 are not C++: two ABNF grammars, two address strings, and one state diagram. 8 more examples compile but never render, because MrDocs stops with an assert on native_socket_option.hpp and publishes no symbol from it. Closes #345 --- .github/workflows/docs.yml | 37 ++- doc/addons/extensions/reference-snippets.lua | 298 ++++++++++++++++++ doc/build_antora.bat | 18 ++ doc/build_antora.sh | 64 ++++ doc/local-playbook.yml | 11 + doc/mrdocs.yml | 19 +- include/boost/corosio/connect.hpp | 8 +- include/boost/corosio/delay.hpp | 10 +- include/boost/corosio/endpoint.hpp | 29 +- include/boost/corosio/host_name.hpp | 8 +- include/boost/corosio/io/io_stream.hpp | 19 +- include/boost/corosio/io_context.hpp | 14 +- include/boost/corosio/ipv4_address.hpp | 5 +- include/boost/corosio/ipv6_address.hpp | 9 +- .../boost/corosio/local_datagram_socket.hpp | 24 +- include/boost/corosio/local_stream.hpp | 6 +- .../boost/corosio/local_stream_acceptor.hpp | 13 +- include/boost/corosio/local_stream_socket.hpp | 13 +- .../corosio/native/native_io_context.hpp | 8 +- .../native/native_local_datagram_socket.hpp | 16 +- .../native/native_local_stream_socket.hpp | 11 +- .../native/native_random_access_file.hpp | 13 +- .../corosio/native/native_socket_option.hpp | 45 +-- .../corosio/native/native_stream_file.hpp | 13 +- .../corosio/native/native_tcp_socket.hpp | 12 +- .../corosio/native/native_udp_socket.hpp | 16 +- include/boost/corosio/openssl_stream.hpp | 20 +- include/boost/corosio/random_access_file.hpp | 11 +- include/boost/corosio/resolver.hpp | 29 +- include/boost/corosio/signal_set.hpp | 10 +- include/boost/corosio/socket_option.hpp | 98 ++---- include/boost/corosio/stream_file.hpp | 18 +- include/boost/corosio/tcp.hpp | 12 +- include/boost/corosio/tcp_acceptor.hpp | 66 +--- include/boost/corosio/tcp_server.hpp | 133 +------- include/boost/corosio/tcp_socket.hpp | 39 +-- include/boost/corosio/timeout.hpp | 7 +- include/boost/corosio/tls_context.hpp | 152 ++------- include/boost/corosio/udp.hpp | 9 +- include/boost/corosio/udp_socket.hpp | 33 +- include/boost/corosio/wait_traits.hpp | 18 +- include/boost/corosio/wolfssl_stream.hpp | 20 +- test/doc/CMakeLists.txt | 85 ++++- test/doc/Jamfile | 25 +- test/doc/doc_warnings.hpp | 55 ++++ test/doc/reference/connect.function.cpp | 53 ++++ test/doc/reference/delay.function.cpp | 60 ++++ test/doc/reference/endpoint.record.cpp | 44 +++ test/doc/reference/host_name.function.cpp | 35 ++ test/doc/reference/io_context.record.cpp | 41 +++ .../reference/io_context_options.record.cpp | 47 +++ test/doc/reference/io_stream.record.cpp | 50 +++ .../ipv4_address__to_string.function.cpp | 32 ++ .../ipv6_address__to_string.function.cpp | 36 +++ .../local_datagram_socket.record.cpp | 67 ++++ test/doc/reference/local_stream.record.cpp | 40 +++ .../local_stream_acceptor.record.cpp | 53 ++++ .../reference/local_stream_socket.record.cpp | 51 +++ test/doc/reference/make_endpoint.function.cpp | 40 +++ .../reference/native_io_context.record.cpp | 42 +++ .../native_local_datagram_socket.record.cpp | 61 ++++ .../native_local_stream_socket.record.cpp | 52 +++ .../native_random_access_file.record.cpp | 56 ++++ .../native_socket_option__boolean.record.cpp | 48 +++ .../native_socket_option__integer.record.cpp | 50 +++ ...ve_socket_option__join_group_v4.record.cpp | 56 ++++ ...ve_socket_option__join_group_v6.record.cpp | 56 ++++ ...e_socket_option__leave_group_v4.record.cpp | 41 +++ ...e_socket_option__leave_group_v6.record.cpp | 41 +++ .../native_socket_option__linger.record.cpp | 46 +++ ..._option__multicast_interface_v4.record.cpp | 40 +++ .../reference/native_stream_file.record.cpp | 55 ++++ .../reference/native_tcp_socket.record.cpp | 58 ++++ .../reference/native_udp_socket.record.cpp | 60 ++++ test/doc/reference/openssl_stream.record.cpp | 73 +++++ .../reference/random_access_file.record.cpp | 47 +++ test/doc/reference/resolver.record.cpp | 51 +++ .../reference/resolver__resolve.function.cpp | 58 ++++ test/doc/reference/signal_set.record.cpp | 44 +++ .../socket_option__broadcast.record.cpp | 43 +++ .../socket_option__join_group_v4.record.cpp | 55 ++++ .../socket_option__join_group_v6.record.cpp | 55 ++++ .../socket_option__keep_alive.record.cpp | 33 ++ .../socket_option__leave_group_v4.record.cpp | 40 +++ .../socket_option__leave_group_v6.record.cpp | 40 +++ .../socket_option__linger.record.cpp | 41 +++ ...ocket_option__multicast_hops_v4.record.cpp | 37 +++ ...ocket_option__multicast_hops_v6.record.cpp | 36 +++ ..._option__multicast_interface_v4.record.cpp | 39 +++ ..._option__multicast_interface_v6.record.cpp | 39 +++ ...ocket_option__multicast_loop_v4.record.cpp | 36 +++ ...ocket_option__multicast_loop_v6.record.cpp | 36 +++ .../socket_option__no_delay.record.cpp | 40 +++ ...ket_option__receive_buffer_size.record.cpp | 36 +++ .../socket_option__reuse_address.record.cpp | 46 +++ .../socket_option__reuse_port.record.cpp | 51 +++ ...socket_option__send_buffer_size.record.cpp | 33 ++ .../socket_option__v6_only.record.cpp | 51 +++ test/doc/reference/stream_file.record.cpp | 52 +++ test/doc/reference/tcp.record.cpp | 50 +++ test/doc/reference/tcp_acceptor.record.cpp | 77 +++++ .../tcp_acceptor__accept.function.cpp | 84 +++++ .../tcp_acceptor__get_option.function.cpp | 34 ++ .../reference/tcp_acceptor__open.function.cpp | 48 +++ .../tcp_acceptor__set_option.function.cpp | 48 +++ test/doc/reference/tcp_server.record.cpp | 154 +++++++++ .../reference/tcp_server__join.function.cpp | 68 ++++ .../tcp_server__set_workers.function.cpp | 79 +++++ .../reference/tcp_server__start.function.cpp | 51 +++ .../tcp_server__tcp_server.function.cpp | 44 +++ test/doc/reference/tcp_socket.record.cpp | 49 +++ .../tcp_socket__connect.function.cpp | 45 +++ .../tcp_socket__get_option.function.cpp | 52 +++ .../tcp_socket__set_option.function.cpp | 35 ++ .../tcp_socket__shutdown.function.cpp | 44 +++ test/doc/reference/timeout.function.cpp | 50 +++ test/doc/reference/tls_context.record.cpp | 61 ++++ .../tls_context__add_crl_file.function.cpp | 55 ++++ .../tls_context__add_verify_path.function.cpp | 41 +++ ...tls_context__load_verify_file.function.cpp | 41 +++ .../tls_context__set_alpn.function.cpp | 36 +++ ...tls_context__set_ciphersuites.function.cpp | 38 +++ ...ntext__set_ciphersuites_tls13.function.cpp | 38 +++ ...ext__set_default_verify_paths.function.cpp | 45 +++ ...ext__set_min_protocol_version.function.cpp | 36 +++ ...ontext__set_password_callback.function.cpp | 71 +++++ ...ontext__set_revocation_policy.function.cpp | 53 ++++ ...text__set_servername_callback.function.cpp | 44 +++ ..._context__set_verify_callback.function.cpp | 73 +++++ .../tls_context__set_verify_mode.function.cpp | 43 +++ .../tls_context__tls_context.function.cpp | 34 ++ ...t__use_certificate_chain_file.function.cpp | 42 +++ ...context__use_certificate_file.function.cpp | 43 +++ .../tls_context__use_pkcs12_file.function.cpp | 51 +++ ...context__use_private_key_file.function.cpp | 41 +++ test/doc/reference/udp.record.cpp | 42 +++ test/doc/reference/udp_socket.record.cpp | 68 ++++ test/doc/reference/wait_traits.record.cpp | 71 +++++ test/doc/reference/wolfssl_stream.record.cpp | 76 +++++ 139 files changed, 5421 insertions(+), 836 deletions(-) create mode 100644 doc/addons/extensions/reference-snippets.lua create mode 100644 test/doc/doc_warnings.hpp create mode 100644 test/doc/reference/connect.function.cpp create mode 100644 test/doc/reference/delay.function.cpp create mode 100644 test/doc/reference/endpoint.record.cpp create mode 100644 test/doc/reference/host_name.function.cpp create mode 100644 test/doc/reference/io_context.record.cpp create mode 100644 test/doc/reference/io_context_options.record.cpp create mode 100644 test/doc/reference/io_stream.record.cpp create mode 100644 test/doc/reference/ipv4_address__to_string.function.cpp create mode 100644 test/doc/reference/ipv6_address__to_string.function.cpp create mode 100644 test/doc/reference/local_datagram_socket.record.cpp create mode 100644 test/doc/reference/local_stream.record.cpp create mode 100644 test/doc/reference/local_stream_acceptor.record.cpp create mode 100644 test/doc/reference/local_stream_socket.record.cpp create mode 100644 test/doc/reference/make_endpoint.function.cpp create mode 100644 test/doc/reference/native_io_context.record.cpp create mode 100644 test/doc/reference/native_local_datagram_socket.record.cpp create mode 100644 test/doc/reference/native_local_stream_socket.record.cpp create mode 100644 test/doc/reference/native_random_access_file.record.cpp create mode 100644 test/doc/reference/native_socket_option__boolean.record.cpp create mode 100644 test/doc/reference/native_socket_option__integer.record.cpp create mode 100644 test/doc/reference/native_socket_option__join_group_v4.record.cpp create mode 100644 test/doc/reference/native_socket_option__join_group_v6.record.cpp create mode 100644 test/doc/reference/native_socket_option__leave_group_v4.record.cpp create mode 100644 test/doc/reference/native_socket_option__leave_group_v6.record.cpp create mode 100644 test/doc/reference/native_socket_option__linger.record.cpp create mode 100644 test/doc/reference/native_socket_option__multicast_interface_v4.record.cpp create mode 100644 test/doc/reference/native_stream_file.record.cpp create mode 100644 test/doc/reference/native_tcp_socket.record.cpp create mode 100644 test/doc/reference/native_udp_socket.record.cpp create mode 100644 test/doc/reference/openssl_stream.record.cpp create mode 100644 test/doc/reference/random_access_file.record.cpp create mode 100644 test/doc/reference/resolver.record.cpp create mode 100644 test/doc/reference/resolver__resolve.function.cpp create mode 100644 test/doc/reference/signal_set.record.cpp create mode 100644 test/doc/reference/socket_option__broadcast.record.cpp create mode 100644 test/doc/reference/socket_option__join_group_v4.record.cpp create mode 100644 test/doc/reference/socket_option__join_group_v6.record.cpp create mode 100644 test/doc/reference/socket_option__keep_alive.record.cpp create mode 100644 test/doc/reference/socket_option__leave_group_v4.record.cpp create mode 100644 test/doc/reference/socket_option__leave_group_v6.record.cpp create mode 100644 test/doc/reference/socket_option__linger.record.cpp create mode 100644 test/doc/reference/socket_option__multicast_hops_v4.record.cpp create mode 100644 test/doc/reference/socket_option__multicast_hops_v6.record.cpp create mode 100644 test/doc/reference/socket_option__multicast_interface_v4.record.cpp create mode 100644 test/doc/reference/socket_option__multicast_interface_v6.record.cpp create mode 100644 test/doc/reference/socket_option__multicast_loop_v4.record.cpp create mode 100644 test/doc/reference/socket_option__multicast_loop_v6.record.cpp create mode 100644 test/doc/reference/socket_option__no_delay.record.cpp create mode 100644 test/doc/reference/socket_option__receive_buffer_size.record.cpp create mode 100644 test/doc/reference/socket_option__reuse_address.record.cpp create mode 100644 test/doc/reference/socket_option__reuse_port.record.cpp create mode 100644 test/doc/reference/socket_option__send_buffer_size.record.cpp create mode 100644 test/doc/reference/socket_option__v6_only.record.cpp create mode 100644 test/doc/reference/stream_file.record.cpp create mode 100644 test/doc/reference/tcp.record.cpp create mode 100644 test/doc/reference/tcp_acceptor.record.cpp create mode 100644 test/doc/reference/tcp_acceptor__accept.function.cpp create mode 100644 test/doc/reference/tcp_acceptor__get_option.function.cpp create mode 100644 test/doc/reference/tcp_acceptor__open.function.cpp create mode 100644 test/doc/reference/tcp_acceptor__set_option.function.cpp create mode 100644 test/doc/reference/tcp_server.record.cpp create mode 100644 test/doc/reference/tcp_server__join.function.cpp create mode 100644 test/doc/reference/tcp_server__set_workers.function.cpp create mode 100644 test/doc/reference/tcp_server__start.function.cpp create mode 100644 test/doc/reference/tcp_server__tcp_server.function.cpp create mode 100644 test/doc/reference/tcp_socket.record.cpp create mode 100644 test/doc/reference/tcp_socket__connect.function.cpp create mode 100644 test/doc/reference/tcp_socket__get_option.function.cpp create mode 100644 test/doc/reference/tcp_socket__set_option.function.cpp create mode 100644 test/doc/reference/tcp_socket__shutdown.function.cpp create mode 100644 test/doc/reference/timeout.function.cpp create mode 100644 test/doc/reference/tls_context.record.cpp create mode 100644 test/doc/reference/tls_context__add_crl_file.function.cpp create mode 100644 test/doc/reference/tls_context__add_verify_path.function.cpp create mode 100644 test/doc/reference/tls_context__load_verify_file.function.cpp create mode 100644 test/doc/reference/tls_context__set_alpn.function.cpp create mode 100644 test/doc/reference/tls_context__set_ciphersuites.function.cpp create mode 100644 test/doc/reference/tls_context__set_ciphersuites_tls13.function.cpp create mode 100644 test/doc/reference/tls_context__set_default_verify_paths.function.cpp create mode 100644 test/doc/reference/tls_context__set_min_protocol_version.function.cpp create mode 100644 test/doc/reference/tls_context__set_password_callback.function.cpp create mode 100644 test/doc/reference/tls_context__set_revocation_policy.function.cpp create mode 100644 test/doc/reference/tls_context__set_servername_callback.function.cpp create mode 100644 test/doc/reference/tls_context__set_verify_callback.function.cpp create mode 100644 test/doc/reference/tls_context__set_verify_mode.function.cpp create mode 100644 test/doc/reference/tls_context__tls_context.function.cpp create mode 100644 test/doc/reference/tls_context__use_certificate_chain_file.function.cpp create mode 100644 test/doc/reference/tls_context__use_certificate_file.function.cpp create mode 100644 test/doc/reference/tls_context__use_pkcs12_file.function.cpp create mode 100644 test/doc/reference/tls_context__use_private_key_file.function.cpp create mode 100644 test/doc/reference/udp.record.cpp create mode 100644 test/doc/reference/udp_socket.record.cpp create mode 100644 test/doc/reference/wait_traits.record.cpp create mode 100644 test/doc/reference/wolfssl_stream.record.cpp diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 69cf82481..891350d72 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,5 +1,6 @@ # # Copyright (c) 2026 Steve Gerbino +# Copyright (c) 2026 Michael Vandeberg # # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -16,13 +17,19 @@ on: - develop* paths: - 'doc/**' + - 'include/**' + - 'test/doc/reference/**' - '*.adoc' - 'README.adoc' + - '.github/workflows/docs.yml' pull_request: paths: - 'doc/**' + - 'include/**' + - 'test/doc/reference/**' - '*.adoc' - 'README.adoc' + - '.github/workflows/docs.yml' jobs: antora: @@ -128,7 +135,11 @@ jobs: cd boost-root/libs/corosio cd doc - bash ./build_antora.sh + # Tee'd purely to keep the build log readable in the step output; + # Antora exits zero even on failure, which is why the checks below + # exist. + set -o pipefail + bash ./build_antora.sh 2>&1 | tee "$RUNNER_TEMP/antora.log" # Antora returns zero even if it fails, so we check if the site directory exists if [ ! -d "build/site" ]; then @@ -136,6 +147,30 @@ jobs: exit 1 fi + # BLOCKING, but deliberately not a count. A MrDocs without the + # extension installed ignores the script entirely and renders the + # reference with no examples while still reporting success, so something + # has to notice. Checking that one known example reached the HTML catches + # that without asking anyone to maintain a number: this example exists + # only in test/doc/reference/socket_option__no_delay.record.cpp, never in + # a header. + - name: Doc-quality - reference examples were injected (BLOCKING) + run: | + set -euo pipefail + site=boost-root/libs/corosio/doc/build/site + if ! grep -rqF disable_nagle_on_a_connected_socket "$site/corosio/reference"; then + echo "No injected example found in the rendered reference." >&2 + echo "The reference-snippets transform did not run, or its output" >&2 + echo "did not reach the HTML. doc/build_antora.sh installs the" >&2 + echo "extension into a MrDocs and exports MRDOCS_ROOT; check that it" >&2 + echo "did, and that the Antora reference extension accepted it -- it" >&2 + echo "logs 'Using local MrDocs' at debug level, and setting a" >&2 + echo "'version' in doc/local-playbook.yml makes it reject a local" >&2 + echo "install and silently download its own instead." >&2 + exit 1 + fi + echo "the rendered reference carries its injected examples" + - name: Create Antora Docs Artifact uses: actions/upload-artifact@v4 with: diff --git a/doc/addons/extensions/reference-snippets.lua b/doc/addons/extensions/reference-snippets.lua new file mode 100644 index 000000000..1cdcf0b98 --- /dev/null +++ b/doc/addons/extensions/reference-snippets.lua @@ -0,0 +1,298 @@ +-- +-- Copyright (c) 2026 Michael Vandeberg +-- +-- Distributed under the Boost Software License, Version 1.0. (See accompanying +-- file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +-- +-- Official repository: https://github.com/cppalliance/corosio +-- + +-- Inject compiled reference examples into the MrDocs corpus. +-- +-- Examples live as tagged regions in checked-in .cpp files under +-- test/doc/reference/, compiled by boost_corosio_doc_tests like every other +-- snippet. This transform reads those regions and injects them into the +-- matching symbol's documentation, so the reference renders an example the +-- normal build has already compiled. Compilation and injection stay +-- independent: CI compiles the files whether or not the docs build, and the +-- docs build injects them whether or not they compile. +-- +-- The file name IS the mapping: a symbol's examples live in +-- +-- test/doc/reference/..cpp +-- +-- where is the qualified name with `boost::corosio::` stripped and +-- `::` replaced by `__`. So boost::corosio::socket_option::no_delay (a record) +-- reads `socket_option__no_delay.record.cpp`. The kind is part of the name +-- because a name alone is ambiguous: `no_delay` also matches its constructors +-- and their overload set. Where even that is ambiguous, the symbol's unique +-- MrDocs anchor is tried first: `..cpp`. +-- +-- Discovery works symbol-first, opening a predicted path, because MrDocs' Lua +-- sandbox blocks io.popen and Lua has no directory listing. +-- +-- A file may hold several tagged regions. Each `@par !example ` marker in +-- a docstring names the region it wants, and that region's code replaces the +-- marker heading where it stands, so an example renders exactly where the +-- docstring puts it. A symbol with no marker is left untouched; a marker naming +-- a region the file does not hold is an error. + +local SOURCE_DEFAULT = "test/doc/reference" +local STRIP_PREFIX = "boost::corosio::" +-- Heading title that marks a position without rendering; see the anchor loop. +local SENTINEL = "!example" + +-- Scalar fields copied when rebuilding a block. `sym.doc.document` accepts only +-- plain tables and rejects the userdata proxies it hands out, so appending to a +-- symbol's documentation means deep-copying every existing block first. A field +-- missing from this list is dropped silently, which is why the injection is +-- verified against a no-transform baseline rather than trusted. +-- `level` is deliberately absent: MrDocs' generic setter refuses to write it +-- ("field 'level' has a type the generic setter cannot yet write"), as an +-- integer or a float. Every heading in this corpus is level 1, which is what +-- `@par` produces and what the templates default to, so omitting it round-trips +-- unchanged -- verified by diffing the rendered corpus against a no-transform +-- baseline. A corpus using deeper headings would need this fixed upstream. +local SCALARS = { + "kind", "literal", "lang", "title", "name", "text", + "href", "anchor", "id", "style", "admonition", "admonish", "symbol", "href_text", + -- ImageInline carries its target in `src`/`alt` and FootnoteReferenceInline + -- its back-link in `label`. Omitted, an image rebuilds as `image:[]` and a + -- footnote reference as a link with no text, orphaning its definition. + "src", "alt", "label", +} + +-- Child blocks hang off more than one field name: a paragraph uses `children`, +-- a list uses `items` (of `listItem`, which in turn uses `blocks`), and an +-- admonition uses `blocks`. Recursing only `children` silently drops list items +-- and note bodies -- caught by the baseline diff, not by any error. +local CONTAINERS = { "children", "items", "blocks" } + +local function fail(msg) + error("[reference-snippets] " .. msg, 0) +end + +local function qualified_name(ctx, sym) + local parts, cur = {}, sym + while cur and cur.name ~= nil do + table.insert(parts, 1, cur.name) + cur = cur.parent and ctx.corpus.get(cur.parent) or nil + end + return table.concat(parts, "::") +end + +-- Whether this library owns the symbol's documentation, and so owns its +-- examples. Three kinds of symbol in the corpus carry `!example` markers this +-- repository can never satisfy: a dependency's own symbols (a downstream +-- library sees every marker in its upstream's headers), the copy MrDocs +-- makes of an inherited member inside the deriving class, whose docstring -- +-- marker included -- comes from the base's header, and anything else outside +-- `boost::corosio::`. All three would trip the fail-closed guard below and +-- abort the docs build. The last is the backstop: `slug()` strips only the +-- corosio prefix, so a dependency symbol MrDocs classifies as neither of the +-- first two would send the build looking for `boost_capy_X.record.cpp`. +-- Their markers are still stripped, just never resolved: a dependency's +-- symbols have no page, but an inherited copy does, and a raw +-- `!example ` heading must never reach it. +local function owned(ctx, sym) + local ok, mode = pcall(function() return sym.extraction end) + if ok and mode ~= nil and tostring(mode) == "dependency" then return false end + local ok2, inherited = pcall(function() return sym.isCopyFromInherited end) + if ok2 and inherited == true then return false end + if qualified_name(ctx, sym):sub(1, #STRIP_PREFIX) ~= STRIP_PREFIX then return false end + return true +end + +local function copy(node) + local out = {} + for _, f in ipairs(SCALARS) do + local ok, v = pcall(function() return node[f] end) + if ok and v ~= nil and type(v) ~= "userdata" and type(v) ~= "table" then + -- Numbers read back as Lua floats (a heading's level is 1.0), and the + -- generic setter rejects a float where the DOM holds an integer. + if type(v) == "number" and math.tointeger(v) then v = math.tointeger(v) end + out[f] = v + end + end + for _, field in ipairs(CONTAINERS) do + local ok, kids = pcall(function() return node[field] end) + if ok and kids ~= nil then + local c = {} + for _, k in ipairs(kids) do c[#c + 1] = copy(k) end + if #c > 0 then out[field] = c end + end + end + return out +end + +-- The first literal found anywhere under a node, used to identify a heading. +local function text_of(node) + local ok, lit = pcall(function() return node.literal end) + if ok and type(lit) == "string" and lit ~= "" then return lit end + local ok2, kids = pcall(function() return node.children end) + if ok2 and kids then + for _, k in ipairs(kids) do + local t = text_of(k) + if t then return t end + end + end + return nil +end + +local function slug(qname) + local s = qname + if s:sub(1, #STRIP_PREFIX) == STRIP_PREFIX then s = s:sub(#STRIP_PREFIX + 1) end + s = s:gsub("::", "__") + -- Anything not an identifier character collapses to `_`, so a call operator + -- (whose qualified name is literally `operator()`) yields a usable file name. + return (s:gsub("[^%w_]+", "_")) +end + +local function dedent(lines) + local margin + for _, l in ipairs(lines) do + if l:match("%S") then + local n = #(l:match("^[ \t]*")) + if margin == nil or n < margin then margin = n end + end + end + local out = {} + for _, l in ipairs(lines) do out[#out + 1] = l:sub((margin or 0) + 1) end + while #out > 0 and not out[1]:match("%S") do table.remove(out, 1) end + while #out > 0 and not out[#out]:match("%S") do table.remove(out) end + return table.concat(out, "\n") +end + +-- Tagged regions in a file, in order. Returns nil when the file does not exist. +local function read_regions(path) + local f = io.open(path, "r") + if not f then return nil end + local lines = {} + for l in f:lines() do lines[#lines + 1] = l end + f:close() + + local out, open_tag, body = {}, nil, nil + for i, l in ipairs(lines) do + local t = l:match("^%s*//%s*tag::([%w_%-]+)%[%]%s*$") + local e = l:match("^%s*//%s*end::([%w_%-]+)%[%]%s*$") + if t then + if open_tag then fail(path .. ":" .. i .. ": tag '" .. t .. "' opens inside '" .. open_tag .. "'") end + open_tag, body = t, {} + elseif e then + if open_tag ~= e then + fail(path .. ":" .. i .. ": end::" .. e .. "[] does not close the open tag") + end + out[#out + 1] = { tag = open_tag, code = dedent(body) } + open_tag, body = nil, nil + elseif open_tag then + body[#body + 1] = l + end + end + if open_tag then fail(path .. ": tag '" .. open_tag .. "' is never closed") end + if #out == 0 then fail(path .. ": file exists but declares no // tag::name[] region") end + return out +end + +-- Rewrite a symbol's documentation, replacing each marker heading in `anchors` +-- with the region `picked` holds for it. A nil `picked` deletes the markers +-- instead, which is what an unowned symbol gets: this repository cannot supply +-- its example, but it must not publish the raw marker either. Returns the number +-- of examples injected. Any `@par Example` heading the marker sat under is left +-- alone -- an empty section is sparse, a literal "!example" is broken. +local function rewrite(sym, seq, anchors, picked) + local nth, out = 0, {} + for i, b in ipairs(seq) do + if anchors[nth + 1] and anchors[nth + 1].at == i then + nth = nth + 1 + if picked then + out[#out + 1] = { kind = "code", literal = picked[nth].code } + end + else + out[#out + 1] = copy(b) + end + end + sym.doc.document = out + return picked and #anchors or 0 +end + +mrdocs.register_transform("reference-snippets", function(ctx) + local root = (ctx.params and ctx.params.source) or SOURCE_DEFAULT + local injected, files, missing = 0, 0, {} + + for _, sym in ipairs(ctx.corpus.symbols) do + if sym.name ~= nil and sym.doc then + local doc = sym.doc.document or {} + + -- Each `@par !example ` marker names the region it wants. The + -- marker is consumed rather than rendered, so the example lands where + -- the docstring puts it, and naming the region keeps overloads that + -- share a file independent of the order MrDocs visits them in -- an + -- order that differs between corpora, and that silently swapped two + -- `armed` examples on the published site. + local seq, anchors = {}, {} + for _, b in ipairs(doc) do seq[#seq + 1] = b end + for i, b in ipairs(seq) do + if b.kind == "heading" then + local t = text_of(b) + local tag = t and t:match("^" .. SENTINEL .. "%s+(%S+)$") + if tag then + anchors[#anchors + 1] = { at = i, tag = tag } + elseif t and t:sub(1, #SENTINEL) == SENTINEL then + -- A marker that does not parse -- a tag with a space in + -- it, or a stray trailing character -- would otherwise + -- go unrecognised, survive unstripped, and publish a + -- heading reading literally "!example ..." while the + -- build reported success. Fail closed like the rest. + fail(string.format("%s: malformed marker heading '%s'; expected '%s ' with a single-word tag", + qualified_name(ctx, sym), t, SENTINEL)) + end + end + end + local at = anchors[1] and anchors[1].at or nil + + if at and not owned(ctx, sym) then + rewrite(sym, seq, anchors, nil) + elseif at then + local base = root .. "/" .. slug(qualified_name(ctx, sym)) + local path = base .. "." .. tostring(sym.anchor) .. ".cpp" + local regions = read_regions(path) + if not regions then + path = base .. "." .. tostring(sym.kind) .. ".cpp" + regions = read_regions(path) + end + if not regions then + -- Fail-closed guard: the docstring promises an example and + -- no file provides one, so the reference would render an + -- empty "Example" section. + missing[#missing + 1] = qualified_name(ctx, sym) .. + " (expected " .. base .. "." .. tostring(sym.kind) .. ".cpp)" + else + files = files + 1 + local by_tag = {} + for _, r in ipairs(regions) do by_tag[r.tag] = r end + local picked = {} + for k, a in ipairs(anchors) do + local r = by_tag[a.tag] + if not r then + fail(string.format("%s has no region tagged '%s', named by %s", + path, a.tag, qualified_name(ctx, sym))) + end + picked[k] = r + end + + injected = injected + rewrite(sym, seq, anchors, picked) + end + end + end + end + + if #missing > 0 then + fail("these symbols carry an @par !example marker with no snippet file:\n " .. + table.concat(missing, "\n ")) + end + -- stderr, not print: the Antora reference extension buffers MrDocs' stdout + -- and discards it when the command succeeds, forwarding only stderr, so a + -- print() here never reaches the docs build log. + io.stderr:write(string.format("[reference-snippets] injected %d example(s) from %d file(s) under %s\n", + injected, files, root)) +end) diff --git a/doc/build_antora.bat b/doc/build_antora.bat index 829f46d10..b5ed0280c 100644 --- a/doc/build_antora.bat +++ b/doc/build_antora.bat @@ -12,6 +12,24 @@ echo Building documentation with Antora... echo Installing npm dependencies... call npm ci +rem The reference examples are injected by addons\extensions\reference-snippets.lua. +rem MrDocs loads extensions only from \share\mrdocs\addons\extensions, so +rem the extension has to be placed inside a MrDocs install. Unlike build_antora.sh +rem this script does not download MrDocs; it only installs the extension into an +rem install the caller supplies through MRDOCS_ROOT. Without it the reference +rem builds with no examples at all and still reports success. +if defined MRDOCS_ROOT ( + if not exist "%MRDOCS_ROOT%\share\mrdocs\addons\extensions" ( + mkdir "%MRDOCS_ROOT%\share\mrdocs\addons\extensions" + ) + copy /Y "addons\extensions\*.lua" "%MRDOCS_ROOT%\share\mrdocs\addons\extensions" >nul + echo MrDocs: %MRDOCS_ROOT% ^(reference-snippets extension installed^) +) else ( + echo WARNING: MRDOCS_ROOT is not set, so reference-snippets.lua cannot be + echo WARNING: installed and the reference will render with no examples. + echo WARNING: Set MRDOCS_ROOT to a MrDocs develop install to inject them. +) + echo Building docs in custom dir... call "C:\Program Files\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" x64 call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" x64 diff --git a/doc/build_antora.sh b/doc/build_antora.sh index 35cdfbaef..18848be74 100644 --- a/doc/build_antora.sh +++ b/doc/build_antora.sh @@ -1,6 +1,7 @@ #!/bin/bash # # Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) +# Copyright (c) 2026 Michael Vandeberg # # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -21,6 +22,69 @@ echo "Building documentation with Antora..." echo "Installing npm dependencies..." npm ci +# The reference examples are injected by addons/extensions/reference-snippets.lua. +# MrDocs loads extensions only from /share/mrdocs/addons/extensions, and +# its `addons-supplemental` config key is recognised but has no effect, so the +# extension has to be placed inside a MrDocs install. Doing it here rather than in +# CI means every caller gets it: this repository's docs workflow, the C++ Alliance +# doc build, and a plain local run. Without it the reference builds with no +# examples at all and reports success. +# +# develop-release rather than a tagged release: MrDocs' extension API postdates +# v0.8.0. The tag is rolling, so this URL always names the current develop build +# and needs no API call or token. +if [ -z "${MRDOCS_ROOT:-}" ]; then + case "$(uname -s)" in + Linux) mrdocs_asset="MrDocs-develop-Linux.tar.gz" ;; + Darwin) mrdocs_asset="MrDocs-develop-Darwin.tar.gz" ;; + *) echo "No MrDocs build for $(uname -s); set MRDOCS_ROOT to an install." >&2 + exit 1 ;; + esac + mrdocs_dir="$(pwd)/build/mrdocs" + # A cached install counts only if both halves are there. An interrupted tar can + # leave bin/mrdocs behind without share/mrdocs, and testing for the binary alone + # would take that half-install for a good one and skip the refetch. Extraction + # therefore starts from an empty directory, so a partial extract can never be + # mistaken for a complete one. + # + # Nothing invalidates this cache on age, and develop-release is a rolling tag, + # so a long-lived tree keeps whichever build it first downloaded and two + # developers can be on different ones. Refetching every build would cost a + # download per run; `rm -rf doc/build/mrdocs` forces a fresh one. + if ! { find "$mrdocs_dir" -type f -name mrdocs -perm -u+x 2>/dev/null | grep -q . && + find "$mrdocs_dir" -type d -path '*/share/mrdocs' 2>/dev/null | grep -q .; }; then + echo "Fetching MrDocs ($mrdocs_asset)" + rm -rf "$mrdocs_dir" + mkdir -p "$mrdocs_dir" + curl -fsSL --retry 3 --retry-delay 2 \ + "https://github.com/cppalliance/mrdocs/releases/download/develop-release/$mrdocs_asset" \ + -o "$mrdocs_dir/mrdocs.tar.gz" + tar -xzf "$mrdocs_dir/mrdocs.tar.gz" -C "$mrdocs_dir" + rm -f "$mrdocs_dir/mrdocs.tar.gz" + fi + mrdocs_bin=$(find "$mrdocs_dir" -type f -name mrdocs -perm -u+x | head -n 1) + if [ -z "$mrdocs_bin" ]; then + echo "MrDocs binary not found under $mrdocs_dir" >&2 + exit 1 + fi + MRDOCS_ROOT=$(dirname "$(dirname "$mrdocs_bin")") + export MRDOCS_ROOT +fi + +# Install the extension into whichever MrDocs will be used, including one the +# caller supplied -- which means writing into that install. A MRDOCS_ROOT +# pointing at a read-only or shared location (/usr/local, a Nix store path) makes +# this fail, and `set -e` stops the build there; point it at a writable copy. +mkdir -p "$MRDOCS_ROOT/share/mrdocs/addons/extensions" +cp addons/extensions/*.lua "$MRDOCS_ROOT/share/mrdocs/addons/extensions/" +echo "MrDocs: $MRDOCS_ROOT (reference-snippets extension installed)" + +# Later CI steps run in their own shells, so the export above does not reach +# them. +if [ -n "${GITHUB_ENV:-}" ]; then + echo "MRDOCS_ROOT=$MRDOCS_ROOT" >> "$GITHUB_ENV" +fi + echo "Building docs..." export PATH="$PATH:$(pwd)/node_modules/.bin" npx antora --clean --fetch "$PLAYBOOK" diff --git a/doc/local-playbook.yml b/doc/local-playbook.yml index 74fef7b9b..adbc75aea 100644 --- a/doc/local-playbook.yml +++ b/doc/local-playbook.yml @@ -25,6 +25,17 @@ antora: using-namespaces: - 'boost::' - require: '@cppalliance/antora-cpp-reference-extension' + # Deliberately NO `version` key. The reference examples are injected by + # doc/addons/extensions/reference-snippets.lua, which needs MrDocs' + # extension API -- not in any tagged release (corpus transforms shipped in + # cppalliance/mrdocs#1196), after v0.8.0. The docs workflow therefore + # installs a develop build with that extension copied in and points here + # via MRDOCS_ROOT, which is the real pin. + # + # Setting `version` DEFEATS that: a local install reports its version as + # `0.8.0+`, which can never satisfy "develop", so the extension + # rejects MRDOCS_ROOT and downloads its own copy -- one without the + # extension, which then renders the reference with no examples at all. dependencies: - name: 'boost' repo: 'https://github.com/boostorg/boost.git' diff --git a/doc/mrdocs.yml b/doc/mrdocs.yml index 2455097be..cd392e57e 100644 --- a/doc/mrdocs.yml +++ b/doc/mrdocs.yml @@ -13,11 +13,15 @@ include-symbols: implementation-defined: - 'boost::corosio::detail' - 'boost::corosio::*::detail' -inaccessible-members: never -inaccessible-bases: never +# `inaccessible-members`/`inaccessible-bases` are gone on develop, which is the +# channel the docs are pinned to (see doc/local-playbook.yml). Their job is done +# by the extract-private* defaults: dropping the two keys left the generated page +# set identical, and the rendered corpus is diffed against a pre-change baseline +# to confirm no inaccessible member or base leaked into the reference. -# Generator -generate: adoc +# Generator. Spelled `generator` since develop; the Antora reference extension +# passes --generator=adoc explicitly in any case. +generator: adoc base-url: https://www.github.com/cppalliance/corosio/blob/develop/ # Style @@ -29,4 +33,11 @@ multipage: true # Sorting sort-members-relational-last: false +# Reference examples are injected by addons/extensions/reference-snippets.lua. +# MrDocs loads extensions from /share/mrdocs/addons/extensions, so the +# doc build installs it there (see doc/build_antora.sh). Setting +# `addons-supplemental` here does NOT work: the key is recognised but only the +# command-line flag reaches extension discovery, and the Antora extension does +# not expose one. + cmake: '-DCMAKE_CXX_STANDARD=20 -DBOOST_COROSIO_MRDOCS_BUILD=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=OFF' diff --git a/include/boost/corosio/connect.hpp b/include/boost/corosio/connect.hpp index d223eb64a..5276a8653 100644 --- a/include/boost/corosio/connect.hpp +++ b/include/boost/corosio/connect.hpp @@ -129,13 +129,7 @@ connect(Socket& s, Iter begin, Iter end, ConnectCondition cond); `Socket::connect`). @par Example - @code - resolver r(ioc); - auto [rec, results] = co_await r.resolve("www.boost.org", "80"); - if (rec) co_return; - tcp_socket s(ioc); - auto [cec, ep] = co_await corosio::connect(s, results); - @endcode + @par !example connect */ template requires std::convertible_to< diff --git a/include/boost/corosio/delay.hpp b/include/boost/corosio/delay.hpp index 133a83df6..d36be3f64 100644 --- a/include/boost/corosio/delay.hpp +++ b/include/boost/corosio/delay.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -330,9 +331,7 @@ class clock_delay_awaitable synchronously. @par Example - @code - auto [ec] = co_await delay(std::chrono::milliseconds(100)); - @endcode + @par !example duration @param dur The duration to wait. @@ -377,10 +376,7 @@ delay(std::chrono::steady_clock::time_point tp) noexcept on the io_context's run thread and must not throw or block. @par Example - @code - auto [ec] = co_await delay( - std::chrono::system_clock::now() + std::chrono::minutes(5)); - @endcode + @par !example system_clock_deadline @tparam Traits The wait-traits policy; `void` selects @ref wait_traits. diff --git a/include/boost/corosio/endpoint.hpp b/include/boost/corosio/endpoint.hpp index e73ad0bca..86526768d 100644 --- a/include/boost/corosio/endpoint.hpp +++ b/include/boost/corosio/endpoint.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -38,21 +39,7 @@ namespace boost::corosio { Shared objects: Safe. @par Example - @code - // IPv4 endpoint - endpoint ep4(ipv4_address::loopback(), 8080); - - // IPv6 endpoint - endpoint ep6(ipv6_address::loopback(), 8080); - - // Port only (defaults to IPv4 any address) - endpoint bind_addr(8080); - - // Create from string - auto [ec, ep] = make_endpoint("192.168.1.1:8080"); - if (ec) - return; - @endcode + @par !example endpoint */ class endpoint { @@ -286,17 +273,7 @@ endpoint_format detect_endpoint_format(std::string_view s) noexcept; @li IPv6 with port (bracketed): `[::1]:8080` @par Example - @code - auto [ec, ep] = make_endpoint("192.168.1.1:8080"); - if (ec) - return; - assert( ep.is_v4() && ep.port() == 8080 ); - - auto [ec6, ep6] = make_endpoint("[::1]:443"); - if (ec6) - return; - assert( ep6.is_v6() && ep6.port() == 443 ); - @endcode + @par !example make_endpoint @param s The string to parse. @return The error code, empty on success, and the parsed diff --git a/include/boost/corosio/host_name.hpp b/include/boost/corosio/host_name.hpp index 76fbbcd6e..37b2d875e 100644 --- a/include/boost/corosio/host_name.hpp +++ b/include/boost/corosio/host_name.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -32,12 +33,7 @@ namespace boost::corosio { Strong guarantee; throws only on allocation failure. @par Example - @code - auto [ec, h] = boost::corosio::host_name(); - if (ec) - return; - std::cout << "running on " << h << "\n"; - @endcode + @par !example host_name @return The error code, empty on success, and the hostname as a UTF-8 string — empty on failure. diff --git a/include/boost/corosio/io/io_stream.hpp b/include/boost/corosio/io/io_stream.hpp index afe389216..060e7ba4f 100644 --- a/include/boost/corosio/io/io_stream.hpp +++ b/include/boost/corosio/io/io_stream.hpp @@ -1,6 +1,7 @@ // // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -51,23 +52,7 @@ namespace boost::corosio { from the same implicit or explicit serialization context. @par Example - @code - // Read until buffer full or EOF - capy::task<> read_all( io_stream& stream, std::span buf ) - { - std::size_t total = 0; - while( total < buf.size() ) - { - auto [ec, n] = co_await stream.read_some( - capy::mutable_buffer( buf.data() + total, buf.size() - total ) ); - if( ec == capy::cond::eof ) - break; - if( ec ) - throw std::system_error( ec ); - total += n; - } - } - @endcode + @par !example io_stream @see io_read_stream, io_write_stream, tcp_socket */ diff --git a/include/boost/corosio/io_context.hpp b/include/boost/corosio/io_context.hpp index 9125f7db8..fe7b94147 100644 --- a/include/boost/corosio/io_context.hpp +++ b/include/boost/corosio/io_context.hpp @@ -72,14 +72,7 @@ enum class locking_mode silently ignored when the active backend does not support them. @par Example - @code - io_context_options opts; - opts.max_events_per_poll = 256; // larger batch per syscall - opts.inline_budget_max = 32; // more speculative completions - opts.thread_pool_size = 4; // more file-I/O workers - - io_context ioc(opts); - @endcode + @par !example configure @see io_context, native_io_context */ @@ -207,10 +200,7 @@ effective_concurrency_hint( choose a specific backend at compile time: @par Example - @code - io_context ioc; // platform default - io_context ioc2(corosio::epoll); // explicit backend - @endcode + @par !example construct @par Preconditions The context must outlive every operation posted or dispatched diff --git a/include/boost/corosio/ipv4_address.hpp b/include/boost/corosio/ipv4_address.hpp index 5ccb40698..1e5dd4164 100644 --- a/include/boost/corosio/ipv4_address.hpp +++ b/include/boost/corosio/ipv4_address.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -146,9 +147,7 @@ class BOOST_COROSIO_DECL ipv4_address /** Return the address as a string in dotted decimal format. @par Example - @code - assert( ipv4_address(0x01020304).to_string() == "1.2.3.4" ); - @endcode + @par !example to_string @return The address as a string. */ diff --git a/include/boost/corosio/ipv6_address.hpp b/include/boost/corosio/ipv6_address.hpp index ca5b71ff9..f80c224d8 100644 --- a/include/boost/corosio/ipv6_address.hpp +++ b/include/boost/corosio/ipv6_address.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -170,13 +171,7 @@ class BOOST_COROSIO_DECL ipv6_address contain surrounding square brackets. @par Example - @code - ipv6_address::bytes_type b = {{ - 0, 1, 0, 2, 0, 3, 0, 4, - 0, 5, 0, 6, 0, 7, 0, 8 }}; - ipv6_address a(b); - assert(a.to_string() == "1:2:3:4:5:6:7:8"); - @endcode + @par !example to_string @return The address as a string. diff --git a/include/boost/corosio/local_datagram_socket.hpp b/include/boost/corosio/local_datagram_socket.hpp index e2d002873..94e1ef1ed 100644 --- a/include/boost/corosio/local_datagram_socket.hpp +++ b/include/boost/corosio/local_datagram_socket.hpp @@ -80,29 +80,7 @@ namespace boost::corosio { send and send_to share the write slot. @par Example - @code - // Connectionless - local_datagram_socket sender(ioc); - if (auto ec = sender.open()) - co_return; - if (auto ec = sender.bind(local_endpoint("/tmp/sender.sock"))) - co_return; - auto [ec, n] = co_await sender.send_to( - capy::const_buffer("hello", 5), - local_endpoint("/tmp/receiver.sock")); - if (ec) - co_return; - - // Connected - local_datagram_socket sock(ioc); - auto [cec] = co_await sock.connect(local_endpoint("/tmp/peer.sock")); - if (cec) - co_return; - auto [ec2, n2] = co_await sock.send( - capy::const_buffer("hi", 2)); - if (ec2) - co_return; - @endcode + @par !example connectionless_and_connected */ class BOOST_COROSIO_DECL local_datagram_socket : public io_object { diff --git a/include/boost/corosio/local_stream.hpp b/include/boost/corosio/local_stream.hpp index edc23703b..c1547ff6e 100644 --- a/include/boost/corosio/local_stream.hpp +++ b/include/boost/corosio/local_stream.hpp @@ -29,11 +29,7 @@ class local_stream_acceptor; the system socket headers. @par Example - @code - local_stream_socket sock(ctx); - if (auto ec = sock.open(local_stream{})) - return; - @endcode + @par !example open_with_protocol @see native_local_stream, local_stream_socket, local_stream_acceptor */ diff --git a/include/boost/corosio/local_stream_acceptor.hpp b/include/boost/corosio/local_stream_acceptor.hpp index f43466d40..29a3e1cb8 100644 --- a/include/boost/corosio/local_stream_acceptor.hpp +++ b/include/boost/corosio/local_stream_acceptor.hpp @@ -65,18 +65,7 @@ enum class bind_option accept operations. @par Example - @code - io_context ioc; - local_stream_acceptor acc(ioc); - if (auto ec = acc.open()) - co_return ec; - if (auto ec = acc.bind(local_endpoint("/tmp/my.sock"), - bind_option::unlink_existing)) - co_return ec; - if (auto ec = acc.listen()) - co_return ec; - auto [aec, peer] = co_await acc.accept(); - @endcode + @par !example bind_listen_accept */ class BOOST_COROSIO_DECL local_stream_acceptor : public io_object { diff --git a/include/boost/corosio/local_stream_socket.hpp b/include/boost/corosio/local_stream_socket.hpp index 8e5eb64cf..a80383934 100644 --- a/include/boost/corosio/local_stream_socket.hpp +++ b/include/boost/corosio/local_stream_socket.hpp @@ -61,18 +61,7 @@ namespace boost::corosio { (epoll, kqueue, select, or IOCP). Satisfies @ref capy::Stream. @par Example - @code - io_context ioc; - local_stream_socket s(ioc); - - auto [ec] = co_await s.connect(local_endpoint("/tmp/my.sock")); - if (ec) - co_return; - - char buf[1024]; - auto [read_ec, n] = co_await s.read_some( - capy::mutable_buffer(buf, sizeof(buf))); - @endcode + @par !example connect_and_read */ class BOOST_COROSIO_DECL local_stream_socket : public io_stream { diff --git a/include/boost/corosio/native/native_io_context.hpp b/include/boost/corosio/native/native_io_context.hpp index 9f60c06ab..58522a116 100644 --- a/include/boost/corosio/native/native_io_context.hpp +++ b/include/boost/corosio/native/native_io_context.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -55,12 +56,7 @@ namespace boost::corosio { Same as the underlying context type. @par Example - @code - #include - - native_io_context ctx; - ctx.poll(); // devirtualized call - @endcode + @par !example poll @see io_context, epoll_t, iocp_t */ diff --git a/include/boost/corosio/native/native_local_datagram_socket.hpp b/include/boost/corosio/native/native_local_datagram_socket.hpp index 9ff10ba8d..e1428fe61 100644 --- a/include/boost/corosio/native/native_local_datagram_socket.hpp +++ b/include/boost/corosio/native/native_local_datagram_socket.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -61,20 +62,7 @@ namespace boost::corosio { Same as @ref local_datagram_socket. @par Example - @code - #include - - native_io_context ctx; - native_local_datagram_socket s(ctx); - if (auto ec = s.open()) - co_return; - if (auto ec = s.bind(local_endpoint("/tmp/recv.sock"))) - co_return; - char buf[1024]; - local_endpoint sender; - auto [ec, n] = co_await s.recv_from( - capy::mutable_buffer(buf, sizeof(buf)), sender); - @endcode + @par !example open_bind_recv @see local_datagram_socket, epoll_t, iocp_t */ diff --git a/include/boost/corosio/native/native_local_stream_socket.hpp b/include/boost/corosio/native/native_local_stream_socket.hpp index 0b2b11615..1d1f87e0a 100644 --- a/include/boost/corosio/native/native_local_stream_socket.hpp +++ b/include/boost/corosio/native/native_local_stream_socket.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -60,15 +61,7 @@ namespace boost::corosio { Same as @ref local_stream_socket. @par Example - @code - #include - - native_io_context ctx; - native_local_stream_socket s(ctx); - auto [ec] = co_await s.connect(local_endpoint("/tmp/my.sock")); - if (ec) - co_return; - @endcode + @par !example connect @see local_stream_socket, epoll_t, iocp_t */ diff --git a/include/boost/corosio/native/native_random_access_file.hpp b/include/boost/corosio/native/native_random_access_file.hpp index 43599c811..833491ef3 100644 --- a/include/boost/corosio/native/native_random_access_file.hpp +++ b/include/boost/corosio/native/native_random_access_file.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -59,17 +60,7 @@ namespace boost::corosio { Same as @ref random_access_file. @par Example - @code - #include - - native_io_context ctx; - native_random_access_file f(ctx); - if (auto ec = f.open("data.bin", file_base::read_only)) - co_return; - char buf[4096]; - auto [ec, n] = co_await f.read_some_at( - 0, capy::mutable_buffer(buf, sizeof(buf))); - @endcode + @par !example native_random_access_file @see random_access_file, epoll_t, iocp_t */ diff --git a/include/boost/corosio/native/native_socket_option.hpp b/include/boost/corosio/native/native_socket_option.hpp index 258061d88..5ad1eacf8 100644 --- a/include/boost/corosio/native/native_socket_option.hpp +++ b/include/boost/corosio/native/native_socket_option.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -65,11 +66,7 @@ namespace boost::corosio::native_socket_option { includes, use `boost::corosio::socket_option` instead. @par Example - @code - sock.set_option( native_socket_option::no_delay( true ) ); - auto nd = sock.get_option(); - bool disabled = nd.value(); // true: Nagle's algorithm is off - @endcode + @par !example boolean @tparam Level The protocol level (e.g. `SOL_SOCKET`, `IPPROTO_TCP`). @tparam Name The option name (e.g. `TCP_NODELAY`, `SO_KEEPALIVE`). @@ -168,11 +165,7 @@ class boolean includes, use `boost::corosio::socket_option` instead. @par Example - @code - sock.set_option( native_socket_option::receive_buffer_size( 65536 ) ); - auto opt = sock.get_option(); - int sz = opt.value(); - @endcode + @par !example integer @tparam Level The protocol level (e.g. `SOL_SOCKET`). @tparam Name The option name (e.g. `SO_RCVBUF`). @@ -338,12 +331,7 @@ class byte_integer version. @par Example - @code - sock.set_option( native_socket_option::linger( true, 5 ) ); - auto opt = sock.get_option(); - if ( opt.enabled() ) - std::cout << "linger timeout: " << opt.timeout() << "s\n"; - @endcode + @par !example linger */ class linger { @@ -471,10 +459,7 @@ using multicast_interface_v6 = integer; /** Join an IPv4 multicast group (IP_ADD_MEMBERSHIP). @par Example - @code - sock.set_option( native_socket_option::join_group_v4( - ipv4_address( "239.255.0.1" ) ) ); - @endcode + @par !example join_group_v4 */ class join_group_v4 { @@ -535,10 +520,7 @@ class join_group_v4 /** Leave an IPv4 multicast group (IP_DROP_MEMBERSHIP). @par Example - @code - sock.set_option( native_socket_option::leave_group_v4( - ipv4_address( "239.255.0.1" ) ) ); - @endcode + @par !example leave_group_v4 */ class leave_group_v4 { @@ -599,10 +581,7 @@ class leave_group_v4 /** Join an IPv6 multicast group (IPV6_JOIN_GROUP). @par Example - @code - sock.set_option( native_socket_option::join_group_v6( - ipv6_address( "ff02::1" ), 0 ) ); - @endcode + @par !example join_group_v6 */ class join_group_v6 { @@ -661,10 +640,7 @@ class join_group_v6 /** Leave an IPv6 multicast group (IPV6_LEAVE_GROUP). @par Example - @code - sock.set_option( native_socket_option::leave_group_v6( - ipv6_address( "ff02::1" ), 0 ) ); - @endcode + @par !example leave_group_v6 */ class leave_group_v6 { @@ -726,10 +702,7 @@ class leave_group_v6 takes an `ipv4_address` identifying the local interface. @par Example - @code - sock.set_option( native_socket_option::multicast_interface_v4( - ipv4_address( "192.168.1.1" ) ) ); - @endcode + @par !example multicast_interface_v4 */ class multicast_interface_v4 { diff --git a/include/boost/corosio/native/native_stream_file.hpp b/include/boost/corosio/native/native_stream_file.hpp index 5192e4b03..479a671d1 100644 --- a/include/boost/corosio/native/native_stream_file.hpp +++ b/include/boost/corosio/native/native_stream_file.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -59,17 +60,7 @@ namespace boost::corosio { Same as @ref stream_file. @par Example - @code - #include - - native_io_context ctx; - native_stream_file f(ctx); - if (auto ec = f.open("data.bin", file_base::read_only)) - co_return; - char buf[4096]; - auto [ec, n] = co_await f.read_some( - capy::mutable_buffer(buf, sizeof(buf))); - @endcode + @par !example native_stream_file @see stream_file, epoll_t, iocp_t */ diff --git a/include/boost/corosio/native/native_tcp_socket.hpp b/include/boost/corosio/native/native_tcp_socket.hpp index f33402b1e..9dc2548fe 100644 --- a/include/boost/corosio/native/native_tcp_socket.hpp +++ b/include/boost/corosio/native/native_tcp_socket.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -59,16 +60,7 @@ namespace boost::corosio { Same as @ref tcp_socket. @par Example - @code - #include - - native_io_context ctx; - native_tcp_socket s(ctx); - auto [ec] = co_await s.connect(ep); - if (ec) - co_return; - auto [ec2, n] = co_await s.read_some(buf); - @endcode + @par !example native_tcp_socket @see tcp_socket, epoll_t, iocp_t */ diff --git a/include/boost/corosio/native/native_udp_socket.hpp b/include/boost/corosio/native/native_udp_socket.hpp index 78f792570..d3528386d 100644 --- a/include/boost/corosio/native/native_udp_socket.hpp +++ b/include/boost/corosio/native/native_udp_socket.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -60,20 +61,7 @@ namespace boost::corosio { Same as @ref udp_socket. @par Example - @code - #include - - native_io_context ctx; - native_udp_socket s(ctx); - if (auto ec = s.open()) - co_return; - if (auto ec = s.bind(endpoint(ipv4_address::any(), 9000))) - co_return; - char buf[1024]; - endpoint sender; - auto [ec, n] = co_await s.recv_from( - capy::mutable_buffer(buf, sizeof(buf)), sender); - @endcode + @par !example native_udp_socket @see udp_socket, epoll_t */ diff --git a/include/boost/corosio/openssl_stream.hpp b/include/boost/corosio/openssl_stream.hpp index aac583994..2d8da64e9 100644 --- a/include/boost/corosio/openssl_stream.hpp +++ b/include/boost/corosio/openssl_stream.hpp @@ -55,25 +55,7 @@ namespace boost::corosio { concurrently); a single-threaded context needs no strand. @par Example - @code - tls_context ctx; - ctx.set_verify_mode(tls_verify_mode::peer); - - corosio::tcp_socket sock(ioc); - auto [ec] = co_await sock.connect(endpoint); - if (ec) - co_return; - - // Reference mode - sock must outlive tls - corosio::openssl_stream tls(&sock, ctx); - tls.set_hostname("example.com"); - auto [hec] = co_await tls.handshake(tls_role::client); - if (hec) - co_return; - - // Or owning mode - tls owns the socket - corosio::openssl_stream tls2(std::move(sock), ctx); - @endcode + @par !example openssl_stream @see tls_stream, wolfssl_stream */ diff --git a/include/boost/corosio/random_access_file.hpp b/include/boost/corosio/random_access_file.hpp index 59a596f5e..846286014 100644 --- a/include/boost/corosio/random_access_file.hpp +++ b/include/boost/corosio/random_access_file.hpp @@ -52,16 +52,7 @@ namespace boost::corosio { operations (open, close, size, resize, etc.). @par Example - @code - io_context ioc; - random_access_file f(ioc); - if (auto ec = f.open("data.bin", file_base::read_only)) - co_return; // report the error - - char buf[4096]; - auto [ec, n] = co_await f.read_some_at( - 0, capy::mutable_buffer(buf, sizeof(buf))); - @endcode + @par !example random_access_file */ class BOOST_COROSIO_DECL random_access_file : public io_object { diff --git a/include/boost/corosio/resolver.hpp b/include/boost/corosio/resolver.hpp index 33c63eb6a..b2f4c6561 100644 --- a/include/boost/corosio/resolver.hpp +++ b/include/boost/corosio/resolver.hpp @@ -171,23 +171,7 @@ operator&=(reverse_flags& a, reverse_flags b) noexcept thread pool. @par Example - @code - io_context ioc; - resolver r(ioc); - - // Using structured bindings - auto [ec, results] = co_await r.resolve("www.example.com", "https"); - if (ec) - co_return; - - for (auto const& entry : results) - std::cout << entry.get_endpoint().port() << std::endl; - - // Or, to convert errors into exceptions: - auto [ec2, results2] = co_await r.resolve("www.example.com", "https"); - if (ec2) - throw std::system_error(ec2); - @endcode + @par !example resolver */ class BOOST_COROSIO_DECL resolver : public io_object { @@ -360,9 +344,7 @@ class BOOST_COROSIO_DECL resolver : public io_object a by-value sink such as @ref connect. @par Example - @code - auto [ec, results] = co_await r.resolve("www.example.com", "https"); - @endcode + @par !example forward_resolve */ [[nodiscard]] auto resolve(std::string_view host, std::string_view service) { @@ -402,12 +384,7 @@ class BOOST_COROSIO_DECL resolver : public io_object `io_result`. @par Example - @code - endpoint ep(ipv4_address({127, 0, 0, 1}), 80); - auto [ec, result] = co_await r.resolve(ep); - if (!ec) - std::cout << result.host_name() << ":" << result.service_name(); - @endcode + @par !example reverse_resolve */ [[nodiscard]] auto resolve(endpoint const& ep) { diff --git a/include/boost/corosio/signal_set.hpp b/include/boost/corosio/signal_set.hpp index 97b6584fa..860dfcb11 100644 --- a/include/boost/corosio/signal_set.hpp +++ b/include/boost/corosio/signal_set.hpp @@ -1,6 +1,7 @@ // // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -72,14 +73,7 @@ namespace boost::corosio { SIGINT, SIGTERM, SIGABRT, SIGFPE, SIGILL, SIGSEGV. @par Example - @code - signal_set signals(ctx, SIGINT, SIGTERM); - auto [ec, signum] = co_await signals.wait(); - if (ec == capy::cond::canceled) - co_return; - if (!ec) - std::cout << "Received signal " << signum << std::endl; - @endcode + @par !example wait_for_shutdown */ class BOOST_COROSIO_DECL signal_set : public io_signal_set { diff --git a/include/boost/corosio/socket_option.hpp b/include/boost/corosio/socket_option.hpp index 26df552a9..7e304b037 100644 --- a/include/boost/corosio/socket_option.hpp +++ b/include/boost/corosio/socket_option.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -300,11 +301,7 @@ class BOOST_COROSIO_DECL byte_integer_option /** Disable Nagle's algorithm (TCP_NODELAY). @par Example - @code - sock.set_option( socket_option::no_delay( true ) ); - auto nd = sock.get_option(); - bool disabled = nd.value(); // true: Nagle's algorithm is off - @endcode + @par !example no_delay */ class BOOST_COROSIO_DECL no_delay : public boolean_option { @@ -322,9 +319,7 @@ class BOOST_COROSIO_DECL no_delay : public boolean_option /** Enable periodic keepalive probes (SO_KEEPALIVE). @par Example - @code - sock.set_option( socket_option::keep_alive( true ) ); - @endcode + @par !example keep_alive */ class BOOST_COROSIO_DECL keep_alive : public boolean_option { @@ -346,9 +341,7 @@ class BOOST_COROSIO_DECL keep_alive : public boolean_option connections (dual-stack mode). @par Example - @code - sock.set_option( socket_option::v6_only( true ) ); - @endcode + @par !example v6_only */ class BOOST_COROSIO_DECL v6_only : public boolean_option { @@ -366,9 +359,7 @@ class BOOST_COROSIO_DECL v6_only : public boolean_option /** Allow local address reuse (SO_REUSEADDR). @par Example - @code - acc.set_option( socket_option::reuse_address( true ) ); - @endcode + @par !example reuse_address */ class BOOST_COROSIO_DECL reuse_address : public boolean_option { @@ -390,12 +381,7 @@ class BOOST_COROSIO_DECL reuse_address : public boolean_option returns an error. @par Example - @code - udp_socket sock( ioc ); - if ( auto ec = sock.open() ) - return; - sock.set_option( socket_option::broadcast( true ) ); - @endcode + @par !example broadcast */ class BOOST_COROSIO_DECL broadcast : public boolean_option { @@ -416,15 +402,7 @@ class BOOST_COROSIO_DECL broadcast : public boolean_option `set_option` throws `std::system_error`. @par Example - @code - if ( auto ec = acc.open( tcp::v6() ) ) - return; - acc.set_option( socket_option::reuse_port( true ) ); - if ( auto ec = acc.bind( endpoint( ipv6_address::any(), 8080 ) ) ) - return; - if ( auto ec = acc.listen() ) - return; - @endcode + @par !example reuse_port */ class BOOST_COROSIO_DECL reuse_port : public boolean_option { @@ -442,11 +420,7 @@ class BOOST_COROSIO_DECL reuse_port : public boolean_option /** Set the receive buffer size (SO_RCVBUF). @par Example - @code - sock.set_option( socket_option::receive_buffer_size( 65536 ) ); - auto opt = sock.get_option(); - int sz = opt.value(); - @endcode + @par !example receive_buffer_size */ class BOOST_COROSIO_DECL receive_buffer_size : public integer_option { @@ -464,9 +438,7 @@ class BOOST_COROSIO_DECL receive_buffer_size : public integer_option /** Set the send buffer size (SO_SNDBUF). @par Example - @code - sock.set_option( socket_option::send_buffer_size( 65536 ) ); - @endcode + @par !example send_buffer_size */ class BOOST_COROSIO_DECL send_buffer_size : public integer_option { @@ -488,12 +460,7 @@ class BOOST_COROSIO_DECL send_buffer_size : public integer_option or the timeout expires. @par Example - @code - sock.set_option( socket_option::linger( true, 5 ) ); - auto opt = sock.get_option(); - if ( opt.enabled() ) - std::cout << "linger timeout: " << opt.timeout() << "s\n"; - @endcode + @par !example linger */ class BOOST_COROSIO_DECL linger { @@ -562,9 +529,7 @@ class BOOST_COROSIO_DECL linger reject the four-byte form with `EINVAL`. Linux accepts either size. @par Example - @code - sock.set_option( socket_option::multicast_loop_v4( true ) ); - @endcode + @par !example multicast_loop_v4 */ class BOOST_COROSIO_DECL multicast_loop_v4 : public byte_boolean_option { @@ -582,9 +547,7 @@ class BOOST_COROSIO_DECL multicast_loop_v4 : public byte_boolean_option /** Enable loopback of outgoing multicast on IPv6 (IPV6_MULTICAST_LOOP). @par Example - @code - sock.set_option( socket_option::multicast_loop_v6( true ) ); - @endcode + @par !example multicast_loop_v6 */ class BOOST_COROSIO_DECL multicast_loop_v6 : public boolean_option { @@ -606,9 +569,7 @@ class BOOST_COROSIO_DECL multicast_loop_v6 : public boolean_option Values are truncated to the 0–255 range. @par Example - @code - sock.set_option( socket_option::multicast_hops_v4( 4 ) ); - @endcode + @par !example multicast_hops_v4 */ class BOOST_COROSIO_DECL multicast_hops_v4 : public byte_integer_option { @@ -626,9 +587,7 @@ class BOOST_COROSIO_DECL multicast_hops_v4 : public byte_integer_option /** Set the multicast hop limit for IPv6 (IPV6_MULTICAST_HOPS). @par Example - @code - sock.set_option( socket_option::multicast_hops_v6( 4 ) ); - @endcode + @par !example multicast_hops_v6 */ class BOOST_COROSIO_DECL multicast_hops_v6 : public integer_option { @@ -646,9 +605,7 @@ class BOOST_COROSIO_DECL multicast_hops_v6 : public integer_option /** Set the outgoing interface for IPv6 multicast (IPV6_MULTICAST_IF). @par Example - @code - sock.set_option( socket_option::multicast_interface_v6( 1 ) ); - @endcode + @par !example multicast_interface_v6 */ class BOOST_COROSIO_DECL multicast_interface_v6 : public integer_option { @@ -666,10 +623,7 @@ class BOOST_COROSIO_DECL multicast_interface_v6 : public integer_option /** Join an IPv4 multicast group (IP_ADD_MEMBERSHIP). @par Example - @code - sock.set_option( socket_option::join_group_v4( - ipv4_address( "239.255.0.1" ) ) ); - @endcode + @par !example join_group_v4 */ class BOOST_COROSIO_DECL join_group_v4 { @@ -716,10 +670,7 @@ class BOOST_COROSIO_DECL join_group_v4 /** Leave an IPv4 multicast group (IP_DROP_MEMBERSHIP). @par Example - @code - sock.set_option( socket_option::leave_group_v4( - ipv4_address( "239.255.0.1" ) ) ); - @endcode + @par !example leave_group_v4 */ class BOOST_COROSIO_DECL leave_group_v4 { @@ -766,10 +717,7 @@ class BOOST_COROSIO_DECL leave_group_v4 /** Join an IPv6 multicast group (IPV6_JOIN_GROUP). @par Example - @code - sock.set_option( socket_option::join_group_v6( - ipv6_address( "ff02::1" ), 0 ) ); - @endcode + @par !example join_group_v6 */ class BOOST_COROSIO_DECL join_group_v6 { @@ -815,10 +763,7 @@ class BOOST_COROSIO_DECL join_group_v6 /** Leave an IPv6 multicast group (IPV6_LEAVE_GROUP). @par Example - @code - sock.set_option( socket_option::leave_group_v6( - ipv6_address( "ff02::1" ), 0 ) ); - @endcode + @par !example leave_group_v6 */ class BOOST_COROSIO_DECL leave_group_v6 { @@ -867,10 +812,7 @@ class BOOST_COROSIO_DECL leave_group_v6 takes an `ipv4_address` identifying the local interface. @par Example - @code - sock.set_option( socket_option::multicast_interface_v4( - ipv4_address( "192.168.1.1" ) ) ); - @endcode + @par !example multicast_interface_v4 */ class BOOST_COROSIO_DECL multicast_interface_v4 { diff --git a/include/boost/corosio/stream_file.hpp b/include/boost/corosio/stream_file.hpp index f85ae5d71..77cd111dd 100644 --- a/include/boost/corosio/stream_file.hpp +++ b/include/boost/corosio/stream_file.hpp @@ -47,23 +47,7 @@ namespace boost::corosio { may be in flight at a time. @par Example - @code - io_context ioc; - stream_file f(ioc); - if (auto ec = f.open("data.bin", file_base::read_only)) - co_return; // report the error - - char buf[4096]; - for (;;) - { - auto [ec, n] = co_await f.read_some( - capy::mutable_buffer(buf, sizeof(buf))); - if (ec == capy::cond::eof) - break; - if (ec) - co_return; - } - @endcode + @par !example stream_file */ class BOOST_COROSIO_DECL stream_file : public io_stream { diff --git a/include/boost/corosio/tcp.hpp b/include/boost/corosio/tcp.hpp index cde0bfa9b..c2ff3e179 100644 --- a/include/boost/corosio/tcp.hpp +++ b/include/boost/corosio/tcp.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -30,16 +31,7 @@ class tcp_acceptor; those headers, use @ref native_tcp. @par Example - @code - tcp_acceptor acc( ioc ); - if ( auto ec = acc.open( tcp::v6() ) ) // IPv6 socket - return; - acc.set_option( socket_option::reuse_address( true ) ); - if ( auto ec = acc.bind( endpoint( ipv6_address::any(), 8080 ) ) ) - return; - if ( auto ec = acc.listen() ) - return; - @endcode + @par !example tcp @see native_tcp, tcp_socket, tcp_acceptor */ diff --git a/include/boost/corosio/tcp_acceptor.hpp b/include/boost/corosio/tcp_acceptor.hpp index b17058c4e..faed4e62b 100644 --- a/include/boost/corosio/tcp_acceptor.hpp +++ b/include/boost/corosio/tcp_acceptor.hpp @@ -1,6 +1,7 @@ // // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -55,32 +56,10 @@ namespace boost::corosio { OS accept APIs via the io_context reactor. @par Example - @code - // Convenience constructor: open + configure + bind + listen - io_context ioc; - tcp_acceptor acc( ioc, endpoint( 8080 ) ); - - tcp_socket peer( ioc ); - auto [ec] = co_await acc.accept( peer ); - if ( !ec ) { - // peer is now a connected socket - auto [ec2, n] = co_await peer.read_some( buf ); - } - @endcode + @par !example convenience_construction @par Example - @code - // Fine-grained setup - tcp_acceptor acc( ioc ); - if ( auto ec = acc.open( tcp::v6() ) ) - return ec; - acc.set_option( socket_option::reuse_address( true ) ); - acc.set_option( socket_option::v6_only( true ) ); - if ( auto ec = acc.bind( endpoint( ipv6_address::any(), 8080 ) ) ) - return ec; - if ( auto ec = acc.listen() ) - return ec; - @endcode + @par !example fine_grained_setup */ class BOOST_COROSIO_DECL tcp_acceptor : public io_object { @@ -303,15 +282,7 @@ class BOOST_COROSIO_DECL tcp_acceptor : public io_object `tcp::v4()`. @par Example - @code - if (auto ec = acc.open( tcp::v6() )) - return; // report the error - acc.set_option( socket_option::reuse_address( true ) ); - if (auto ec = acc.bind( endpoint( ipv6_address::any(), 8080 ) )) - return; - if (auto ec = acc.listen()) - return; - @endcode + @par !example open @see bind, listen @@ -401,13 +372,7 @@ class BOOST_COROSIO_DECL tcp_acceptor : public io_object awaitable. @par Example - @code - tcp_socket peer(ioc); - auto [ec] = co_await acc.accept(peer); - if (ec) - co_return; - auto [wec, n] = co_await peer.write_some(buffer); - @endcode + @par !example accept_into_a_reused_socket @see accept() */ @@ -448,12 +413,7 @@ class BOOST_COROSIO_DECL tcp_acceptor : public io_object This acceptor must outlive the returned awaitable. @par Example - @code - auto [ec, peer] = co_await acc.accept(); - if (ec) - co_return; - auto [wec, n] = co_await peer.write_some(buffer); - @endcode + @par !example accept_returning_a_new_socket @see accept(tcp_socket&) */ @@ -590,15 +550,7 @@ class BOOST_COROSIO_DECL tcp_acceptor : public io_object `listen()`, such as `socket_option::reuse_port`. @par Example - @code - if ( auto ec = acc.open( tcp::v6() ) ) - return ec; - acc.set_option( socket_option::reuse_port( true ) ); - if ( auto ec = acc.bind( endpoint( ipv6_address::any(), 8080 ) ) ) - return ec; - if ( auto ec = acc.listen() ) - return ec; - @endcode + @par !example set_option @param opt The option to set. @@ -623,9 +575,7 @@ class BOOST_COROSIO_DECL tcp_acceptor : public io_object Retrieves the current value of a type-safe socket option. @par Example - @code - auto opt = acc.get_option(); - @endcode + @par !example get_option @return The current option value. diff --git a/include/boost/corosio/tcp_server.hpp b/include/boost/corosio/tcp_server.hpp index 4e51e9a18..1b2606af4 100644 --- a/include/boost/corosio/tcp_server.hpp +++ b/include/boost/corosio/tcp_server.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -66,41 +67,17 @@ namespace boost::corosio { @endcode @par Running the Server - @code - io_context ioc; - tcp_server srv(ioc, ioc.get_executor()); - srv.set_workers(make_workers(ioc, 100)); - if (auto ec = srv.bind(endpoint{ipv4_address::any(), 8080})) - return; - srv.start(); - ioc.run(); // Blocks until all work completes - @endcode + @par !example running_the_server @par Graceful Shutdown To shut down gracefully, call @ref stop then drain the io_context: - @code - // From a signal handler or timer callback: - srv.stop(); - - // ioc.run() returns after pending work drains. - // Then from the thread that called ioc.run(): - srv.join(); // Wait for accept loops to finish - @endcode + @par !example graceful_shutdown @par Restart After Stop The server can be restarted after a complete shutdown cycle. - You must drain the io_context and call @ref join before restarting: - @code - srv.start(); - ioc.run_for( 10s ); // Run for a while - srv.stop(); // Signal shutdown - ioc.run(); // REQUIRED: drain pending completions - srv.join(); // REQUIRED: wait for accept loops - - // Now safe to restart - srv.start(); - ioc.run(); - @endcode + You must drain the io_context, call @ref join, and restart the + io_context itself (`ioc.restart()`) before restarting: + @par !example restart_after_stop @par WARNING: What NOT to Do - Do NOT call @ref join from inside a worker coroutine (deadlock). @@ -109,43 +86,7 @@ namespace boost::corosio { - Do NOT call `ioc.stop()` for graceful shutdown; use @ref stop instead. @par Example - @code - class my_worker : public tcp_server::worker_base - { - corosio::tcp_socket sock_; - capy::any_executor ex_; - public: - my_worker(io_context& ctx) - : sock_(ctx) - , ex_(ctx.get_executor()) - { - } - - corosio::tcp_socket& socket() override { return sock_; } - - void run(launcher launch) override - { - launch(ex_, [](corosio::tcp_socket* sock) -> capy::task<> - { - // handle connection using sock - co_return; - }(&sock_)); - } - }; - - auto make_workers(io_context& ctx, int n) - { - std::vector> v; - v.reserve(n); - for(int i = 0; i < n; ++i) - v.push_back(std::make_unique(ctx)); - return v; - } - - io_context ioc; - tcp_server srv(ioc, ioc.get_executor()); - srv.set_workers(make_workers(ioc, 100)); - @endcode + @par !example custom_worker @see worker_base, set_workers, launcher */ @@ -593,13 +534,7 @@ class BOOST_COROSIO_DECL tcp_server @param ex The executor for dispatching coroutines. @par Example - @code - tcp_server srv(ctx, ctx.get_executor()); - srv.set_workers(make_workers(ctx, 100)); - if (auto ec = srv.bind(endpoint{...})) - return; - srv.start(); - @endcode + @par !example tcp_server */ template tcp_server(Ctx& ctx, Ex ex) : impl_(make_impl(ctx)) @@ -654,12 +589,7 @@ class BOOST_COROSIO_DECL tcp_server support `std::to_address()` yielding `worker_base*`. @par Example - @code - std::vector> workers; - for(int i = 0; i < 100; ++i) - workers.push_back(std::make_unique(ctx)); - srv.set_workers(std::move(workers)); - @endcode + @par !example set_workers */ template requires std::convertible_to< @@ -693,7 +623,8 @@ class BOOST_COROSIO_DECL tcp_server @par Preconditions - At least one endpoint bound via @ref bind. - Workers provided via @ref set_workers. - - If restarting, @ref join must have completed first. + - If restarting, @ref join must have completed first, and the + io_context must have been restarted (`ioc.restart()`). @par Effects Creates one accept coroutine per bound endpoint. Each coroutine @@ -702,17 +633,7 @@ class BOOST_COROSIO_DECL tcp_server @par Restart Sequence To restart after stopping, complete the full shutdown cycle: - @code - srv.start(); - ioc.run_for( 1s ); - srv.stop(); // 1. Signal shutdown - ioc.run(); // 2. Drain remaining completions - srv.join(); // 3. Wait for accept loops - - // Now safe to restart - srv.start(); - ioc.run(); - @endcode + @par !example start @par Thread Safety Not thread safe. @@ -782,32 +703,12 @@ class BOOST_COROSIO_DECL tcp_server state and may be restarted via @ref start. @par Example (Correct Usage) - @code - // main thread - srv.start(); - ioc.run(); // Blocks until work completes - srv.join(); // Safe: called after ioc.run() returns - @endcode - - @par WARNING: Deadlock Scenarios - Calling `join()` from the wrong context causes deadlock: - - @code - // WRONG: calling join() from inside a worker coroutine - void run( launcher launch ) override - { - launch( ex, [this]() -> capy::task<> - { - srv_.join(); // DEADLOCK: blocks the executor - co_return; - }()); - } + @par !example correct_usage + + @par WARNING: Deadlock Scenario + Calling `join()` from inside a worker coroutine deadlocks: - // WRONG: calling join() while ioc.run() is still active - std::thread t( [&]{ ioc.run(); } ); - srv.stop(); - srv.join(); // DEADLOCK: ioc.run() still running in thread t - @endcode + @par !example deadlock_scenarios @par Thread Safety May be called from any thread, but will deadlock if called diff --git a/include/boost/corosio/tcp_socket.hpp b/include/boost/corosio/tcp_socket.hpp index 1117dc067..330ca81e4 100644 --- a/include/boost/corosio/tcp_socket.hpp +++ b/include/boost/corosio/tcp_socket.hpp @@ -1,6 +1,7 @@ // // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -60,20 +61,7 @@ namespace boost::corosio { kqueue). Satisfies @ref capy::Stream. @par Example - @code - io_context ioc; - tcp_socket s(ioc); - - // Using structured bindings - auto [ec] = co_await s.connect( - endpoint(ipv4_address::loopback(), 8080)); - if (ec) - co_return; - - char buf[1024]; - auto [read_ec, n] = co_await s.read_some( - capy::mutable_buffer(buf, sizeof(buf))); - @endcode + @par !example connect_and_read */ class BOOST_COROSIO_DECL tcp_socket : public io_stream { @@ -384,12 +372,7 @@ class BOOST_COROSIO_DECL tcp_socket : public io_stream This socket must outlive the returned awaitable. @par Example - @code - // Socket opened automatically with correct address family: - auto [ec] = co_await s.connect(endpoint); - if (ec) - co_return; - @endcode + @par !example connect */ [[nodiscard]] auto connect(endpoint ep) { @@ -520,11 +503,7 @@ class BOOST_COROSIO_DECL tcp_socket : public io_stream Use the portable condition test rather than comparing error codes directly: - @code - auto [ec, n] = co_await sock.read_some(buffer); - if (ec == capy::cond::eof) - co_return; // Peer closed their send direction - @endcode + @par !example shutdown Failures such as a peer that already disconnected are normal runtime conditions and are reported through the @@ -543,10 +522,7 @@ class BOOST_COROSIO_DECL tcp_socket : public io_stream The option type encodes the protocol level and option name. @par Example - @code - sock.set_option( socket_option::no_delay( true ) ); - sock.set_option( socket_option::receive_buffer_size( 65536 ) ); - @endcode + @par !example set_option @param opt The option to set. @@ -571,10 +547,7 @@ class BOOST_COROSIO_DECL tcp_socket : public io_stream Retrieves the current value of a type-safe socket option. @par Example - @code - auto nd = sock.get_option(); - bool disabled = nd.value(); // true: Nagle's algorithm is off - @endcode + @par !example get_option @return The current option value. diff --git a/include/boost/corosio/timeout.hpp b/include/boost/corosio/timeout.hpp index c8274a225..f4ab1ef69 100644 --- a/include/boost/corosio/timeout.hpp +++ b/include/boost/corosio/timeout.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -46,11 +47,7 @@ namespace boost::corosio { cross-thread cancellation. @par Example - @code - auto [ec, n] = co_await timeout(sock.read_some(buf), 50ms); - if (ec == capy::cond::timeout) - co_return; - @endcode + @par !example timeout @param a The awaitable to race against the deadline. @param dur The maximum duration to wait, measured from diff --git a/include/boost/corosio/tls_context.hpp b/include/boost/corosio/tls_context.hpp index ae64ca204..468ce100f 100644 --- a/include/boost/corosio/tls_context.hpp +++ b/include/boost/corosio/tls_context.hpp @@ -189,6 +189,11 @@ struct tls_context_data; tls_context_data const& get_tls_context_data(tls_context const&) noexcept; } // namespace detail +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4251) // shared_ptr needs dll-interface +#endif + /** A portable TLS context for certificate and settings storage. The `tls_context` class provides a backend-agnostic interface for @@ -224,27 +229,10 @@ tls_context_data const& get_tls_context_data(tls_context const&) noexcept; any thread is creating streams from it. @par Example - @code - // Create a client context with system trust anchors - corosio::tls_context ctx; - if (auto ec = ctx.set_default_verify_paths()) - co_return; - if (auto ec = ctx.set_verify_mode( corosio::tls_verify_mode::peer )) - co_return; - - // Use with a TLS stream - corosio::openssl_stream secure( &sock, ctx ); - secure.set_hostname( "example.com" ); - if (auto [ec] = co_await secure.handshake( corosio::tls_role::client ); ec) - co_return; - @endcode + @par !example tls_context @see tls_role */ -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4251) // shared_ptr needs dll-interface -#endif class BOOST_COROSIO_DECL tls_context { struct implementation; @@ -262,9 +250,7 @@ class BOOST_COROSIO_DECL tls_context and verification. @par Example - @code - corosio::tls_context ctx; - @endcode + @par !example tls_context */ tls_context(); @@ -359,11 +345,7 @@ class BOOST_COROSIO_DECL tls_context a malformed certificate surfaces as a handshake failure. @par Example - @code - if (auto ec = ctx.use_certificate_file( - "server.crt", tls_file_format::pem )) - return; - @endcode + @par !example use_certificate_file @see use_certificate @see use_private_key_file @@ -401,10 +383,7 @@ class BOOST_COROSIO_DECL tls_context malformed chain surfaces as a handshake failure. @par Example - @code - if (auto ec = ctx.use_certificate_chain_file( "fullchain.pem" )) - return; - @endcode + @par !example use_certificate_chain_file @see use_certificate_chain */ @@ -453,11 +432,7 @@ class BOOST_COROSIO_DECL tls_context handshake failure. @par Example - @code - if (auto ec = ctx.use_private_key_file( - "server.key", tls_file_format::pem )) - return; - @endcode + @par !example use_private_key_file @see use_private_key @see set_password_callback @@ -508,10 +483,7 @@ class BOOST_COROSIO_DECL tls_context sent during the handshake on both backends. @par Example - @code - if (auto ec = ctx.use_pkcs12_file( "credentials.pfx", "secret" )) - return; - @endcode + @par !example use_pkcs12_file @see use_pkcs12 */ @@ -551,11 +523,7 @@ class BOOST_COROSIO_DECL tls_context built; malformed certificates surface as a handshake failure. @par Example - @code - if (auto ec = ctx.load_verify_file( - "/etc/ssl/certs/ca-certificates.crt" )) - return; - @endcode + @par !example load_verify_file @see add_certificate_authority @see add_verify_path @@ -581,10 +549,7 @@ class BOOST_COROSIO_DECL tls_context is skipped rather than reported here. @par Example - @code - if (auto ec = ctx.add_verify_path( "/etc/ssl/certs" )) - return; - @endcode + @par !example add_verify_path @see load_verify_file @see set_default_verify_paths @@ -615,13 +580,7 @@ class BOOST_COROSIO_DECL tls_context system store is unavailable and this call has no effect. @par Example - @code - // Trust the same CAs as the system - if (auto ec = ctx.set_default_verify_paths()) - return; - if (auto ec = ctx.set_verify_mode( tls_verify_mode::peer )) - return; - @endcode + @par !example set_default_verify_paths @see load_verify_file @see add_verify_path @@ -644,11 +603,7 @@ class BOOST_COROSIO_DECL tls_context native context is first built. @par Example - @code - // Require TLS 1.3 minimum - if (auto ec = ctx.set_min_protocol_version( tls_version::tls_1_3 )) - return; - @endcode + @par !example set_min_protocol_version @see set_max_protocol_version */ @@ -686,11 +641,7 @@ class BOOST_COROSIO_DECL tls_context surfaces as a handshake failure. @par Example - @code - // TLS 1.2 cipher suites (OpenSSL format) - if (auto ec = ctx.set_ciphersuites( "ECDHE+AESGCM:ECDHE+CHACHA20" )) - return; - @endcode + @par !example set_ciphersuites @note This configures cipher suites for TLS 1.2 and below. For TLS 1.3, use @ref set_ciphersuites_tls13. @@ -710,11 +661,7 @@ class BOOST_COROSIO_DECL tls_context surfaces as a handshake failure. @par Example - @code - if (auto ec = ctx.set_ciphersuites_tls13( - "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256" )) - return; - @endcode + @par !example set_ciphersuites_tls13 @note On the WolfSSL backend, TLS 1.2 and TLS 1.3 suites share a single cipher list; this call and @ref set_ciphersuites are @@ -744,11 +691,7 @@ class BOOST_COROSIO_DECL tls_context than negotiate nothing silently. @par Example - @code - // Prefer HTTP/2, fall back to HTTP/1.1 - if (auto ec = ctx.set_alpn( { "h2", "http/1.1" } )) - return; - @endcode + @par !example set_alpn */ [[nodiscard]] std::error_code set_alpn(std::initializer_list protocols); @@ -767,12 +710,7 @@ class BOOST_COROSIO_DECL tls_context context is first built. @par Example - @code - // Verify peer certificate (typical for clients; servers doing - // mTLS use tls_verify_mode::require_peer instead) - if (auto ec = ctx.set_verify_mode( tls_verify_mode::peer )) - return; - @endcode + @par !example set_verify_mode @see tls_verify_mode */ @@ -832,20 +770,7 @@ class BOOST_COROSIO_DECL tls_context `std::errc::function_not_supported` (see Backend Support). @par Example - @code - if (auto ec = ctx.set_verify_mode( tls_verify_mode::peer )) - return; - ctx.set_verify_callback( - []( bool preverified, verify_context& ctx ) -> bool - { - if( ! preverified ) - return false; - // Pin: accept only a certificate whose DER matches. - auto der = ctx.certificate(); - return der.size() == expected_pin.size() && - std::equal( der.begin(), der.end(), expected_pin.begin() ); - }); - @endcode + @par !example set_verify_callback @see verify_context @see set_verify_mode @@ -867,15 +792,7 @@ class BOOST_COROSIO_DECL tls_context connection or `false` to reject it with an alert. @par Example - @code - // Accept connections for specific domains only - ctx.set_servername_callback( - []( std::string_view hostname ) -> bool - { - return hostname == "api.example.com" || - hostname == "www.example.com"; - }); - @endcode + @par !example set_servername_callback @note For virtual hosting with different certificates per hostname, create separate contexts and select the appropriate one before @@ -941,10 +858,7 @@ class BOOST_COROSIO_DECL tls_context build). @par Example - @code - if (auto ec = ctx.add_crl_file( "issuer.crl" )) - return; - @endcode + @par !example add_crl_file @see add_crl @see set_revocation_policy @@ -959,13 +873,7 @@ class BOOST_COROSIO_DECL tls_context @param policy The revocation checking policy. @par Example - @code - // Require successful revocation check - ctx.set_revocation_policy( tls_revocation_policy::hard_fail ); - - // Check but allow unknown status - ctx.set_revocation_policy( tls_revocation_policy::soft_fail ); - @endcode + @par !example set_revocation_policy @note Revocation is checked via CRLs supplied with @ref add_crl / @ref add_crl_file. `soft_fail` accepts a certificate whose @@ -999,19 +907,7 @@ class BOOST_COROSIO_DECL tls_context returns the password string. @par Example - @code - ctx.set_password_callback( - []( std::size_t max_len, tls_password_purpose purpose ) - { - // In practice, prompt user or read from secure storage - return std::string( "my-key-password" ); - }); - - // Now load encrypted key - if (auto ec = ctx.use_private_key_file( - "encrypted.key", tls_file_format::pem )) - return; - @endcode + @par !example set_password_callback @see tls_password_purpose */ diff --git a/include/boost/corosio/udp.hpp b/include/boost/corosio/udp.hpp index 7157fcfc5..d40897983 100644 --- a/include/boost/corosio/udp.hpp +++ b/include/boost/corosio/udp.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -29,13 +30,7 @@ class udp_socket; those headers, use @ref native_udp. @par Example - @code - udp_socket sock( ioc ); - if ( auto ec = sock.open( udp::v4() ) ) - return; - if ( auto ec = sock.bind( endpoint( ipv4_address::any(), 9000 ) ) ) - return; - @endcode + @par !example udp @see native_udp, udp_socket */ diff --git a/include/boost/corosio/udp_socket.hpp b/include/boost/corosio/udp_socket.hpp index 63324e475..c9ecce58f 100644 --- a/include/boost/corosio/udp_socket.hpp +++ b/include/boost/corosio/udp_socket.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -63,37 +64,7 @@ namespace boost::corosio { One send_to and one recv_from may be in flight simultaneously. @par Example - @code - // Connectionless mode - io_context ioc; - udp_socket sock( ioc ); - if ( auto ec = sock.open( udp::v4() ) ) - co_return; - if ( auto ec = sock.bind( endpoint( ipv4_address::any(), 9000 ) ) ) - co_return; - - char buf[1024]; - endpoint sender; - auto [ec, n] = co_await sock.recv_from( - capy::mutable_buffer( buf, sizeof( buf ) ), sender ); - if ( ec ) - co_return; - auto [sec, sn] = co_await sock.send_to( - capy::const_buffer( buf, n ), sender ); - if ( sec ) - co_return; - - // Connected mode - udp_socket csock( ioc ); - auto [cec] = co_await csock.connect( - endpoint( ipv4_address::loopback(), 9000 ) ); - if ( cec ) - co_return; - auto [wec, wn] = co_await csock.send( - capy::const_buffer( buf, n ) ); - if ( wec ) - co_return; - @endcode + @par !example udp_socket */ class BOOST_COROSIO_DECL udp_socket : public io_object { diff --git a/include/boost/corosio/wait_traits.hpp b/include/boost/corosio/wait_traits.hpp index d6e929f01..5d6c6f2d4 100644 --- a/include/boost/corosio/wait_traits.hpp +++ b/include/boost/corosio/wait_traits.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -27,22 +28,7 @@ namespace boost::corosio { with the machine's monotonic clock. @par Example - @code - // Observe wall-clock steps within one second - struct capped_traits - { - static std::chrono::system_clock::duration - to_wait_duration(std::chrono::system_clock::duration d) - { - return (std::min)(d, - std::chrono::system_clock::duration( - std::chrono::seconds(1))); - } - }; - - auto [ec] = co_await delay( - std::chrono::system_clock::now() + std::chrono::hours(1)); - @endcode + @par !example capped_traits @tparam Clock The clock type whose durations are converted. diff --git a/include/boost/corosio/wolfssl_stream.hpp b/include/boost/corosio/wolfssl_stream.hpp index fbbfe8fce..127b05387 100644 --- a/include/boost/corosio/wolfssl_stream.hpp +++ b/include/boost/corosio/wolfssl_stream.hpp @@ -55,25 +55,7 @@ namespace boost::corosio { concurrently); a single-threaded context needs no strand. @par Example - @code - tls_context ctx; - ctx.set_verify_mode(tls_verify_mode::peer); - - corosio::tcp_socket sock(ioc); - auto [ec] = co_await sock.connect(endpoint); - if (ec) - co_return; - - // Reference mode - sock must outlive tls - corosio::wolfssl_stream tls(&sock, ctx); - tls.set_hostname("example.com"); - auto [hec] = co_await tls.handshake(tls_role::client); - if (hec) - co_return; - - // Or owning mode - tls owns the socket - corosio::wolfssl_stream tls2(std::move(sock), ctx); - @endcode + @par !example wolfssl_stream @see tls_stream, openssl_stream */ diff --git a/test/doc/CMakeLists.txt b/test/doc/CMakeLists.txt index c56aa7374..5fc74f369 100644 --- a/test/doc/CMakeLists.txt +++ b/test/doc/CMakeLists.txt @@ -1,5 +1,6 @@ # # Copyright (c) 2026 Steve Gerbino +# Copyright (c) 2026 Michael Vandeberg # # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -11,11 +12,82 @@ # files by tag through the Antora collector, so a fragment that fails to # build here is a fragment that would render broken on the site. +# Jamfile sets extra and on for this +# directory, and the b2 leg is a hard CI gate. Mirror that posture on the +# doc-test targets so a local CMake build fails on the same warnings; a +# CMake build that cannot fail on warnings is not a check of these files. +# Scoped to this directory: the library and the main test suite keep their +# own settings. +function(boost_corosio_doc_warnings_as_errors target) + if(MSVC) + target_compile_options(${target} PRIVATE /W4 /WX) + else() + target_compile_options(${target} PRIVATE -Wall -Wextra -Werror) + # GCC mis-analyzes the structured-binding-from-co_await idiom in + # coroutine frames; test/doc/Jamfile carries the same exemption. + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + target_compile_options(${target} PRIVATE -Wno-maybe-uninitialized) + endif() + endif() +endfunction() + file(GLOB SNIPPETS CONFIGURE_DEPENDS snippets/*.cpp) -set(PFILES ${SNIPPETS} CMakeLists.txt Jamfile) + +# Reference examples: injected into the MrDocs reference by +# doc/addons/extensions/reference-snippets.lua, and compiled here so an example +# in the reference is one the build has already checked. Same target as the +# page snippets -- each file keeps its code in an anonymous namespace, so +# independently authored examples that both define example() do not collide. +# +# They are compiled and never run: several would block on real I/O if executed, +# and some demonstrate a property with static_assert alone. Under coverage that +# makes them worse than useless. Compiling an example instantiates whatever +# header templates it names, and nothing then calls it, so the lines land in +# include/ as instantiated-but-never-entered and read as lost coverage. +# +# Detected from the flags the coverage jobs already pass, so a coverage build +# needs no extra argument of its own. Still an option, so it can be forced +# either way by hand. +# +# This is the guard that matters: every coverage leg corosio runs today builds +# with CMake and passes --coverage through cxxflags, both ci.yml's coverage +# matrix entries and code-coverage.yml. test/doc/Jamfile carries a parallel +# guard defensively, in case a b2 coverage leg is ever added. +string(TOUPPER "${CMAKE_BUILD_TYPE}" BOOST_COROSIO_BUILD_TYPE_UPPER) +# Every route the flag can arrive by: the cache variables, the per-config +# variables, the linker flags (coverage needs it at link time too), and the +# environment, since CMake seeds CMAKE__FLAGS from CXXFLAGS/CFLAGS only on +# the first configure of a fresh tree. +set(BOOST_COROSIO_COVERAGE_FLAGS + "${CMAKE_CXX_FLAGS} ${CMAKE_C_FLAGS}" + "${CMAKE_CXX_FLAGS_${BOOST_COROSIO_BUILD_TYPE_UPPER}}" + "${CMAKE_C_FLAGS_${BOOST_COROSIO_BUILD_TYPE_UPPER}}" + "${CMAKE_EXE_LINKER_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS}" + "$ENV{CXXFLAGS} $ENV{CFLAGS} $ENV{LDFLAGS}") +if(BOOST_COROSIO_COVERAGE_FLAGS MATCHES + "(--coverage|-fprofile-arcs|-ftest-coverage|-fprofile-instr-generate)") + set(BOOST_COROSIO_REFERENCE_SNIPPETS_DEFAULT OFF) +else() + set(BOOST_COROSIO_REFERENCE_SNIPPETS_DEFAULT ON) +endif() +option(BOOST_COROSIO_BUILD_REFERENCE_SNIPPETS + "Compile the reference examples in test/doc/reference (off under coverage)" + ${BOOST_COROSIO_REFERENCE_SNIPPETS_DEFAULT}) + +if(BOOST_COROSIO_BUILD_REFERENCE_SNIPPETS) + file(GLOB REFERENCE_SNIPPETS CONFIGURE_DEPENDS reference/*.cpp) +else() + set(REFERENCE_SNIPPETS) + message(STATUS + "[reference-examples] not compiled: they are never executed, and under " + "coverage they only add never-entered template instantiations") +endif() + +set(PFILES ${SNIPPETS} ${REFERENCE_SNIPPETS} doc_warnings.hpp CMakeLists.txt Jamfile) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "" FILES ${PFILES}) add_executable(boost_corosio_doc_tests ${PFILES}) +boost_corosio_doc_warnings_as_errors(boost_corosio_doc_tests) target_link_libraries( boost_corosio_doc_tests PRIVATE Boost::capy_test_suite_main @@ -26,6 +98,16 @@ if (WolfSSL_FOUND) target_compile_definitions(boost_corosio_doc_tests PRIVATE BOOST_COROSIO_HAS_WOLFSSL=1) endif() +# openssl_stream.record.cpp needs the same treatment: its templated +# constructor calls a non-inline member (make_implementation) defined only +# in boost_corosio_openssl, so leaving this target unlinked would break a +# GCC/debug CI leg even though it happens to link here (see the file's own +# comment for how that was verified). +if (OpenSSL_FOUND) + target_link_libraries(boost_corosio_doc_tests PRIVATE boost_corosio_openssl) + target_compile_definitions(boost_corosio_doc_tests + PRIVATE BOOST_COROSIO_HAS_OPENSSL=1) +endif() boost_capy_test_suite_discover_tests(boost_corosio_doc_tests) add_dependencies(tests boost_corosio_doc_tests) @@ -46,6 +128,7 @@ file(GLOB DOC_PROGRAMS CONFIGURE_DEPENDS programs/*.cpp) foreach(src ${DOC_PROGRAMS}) get_filename_component(name ${src} NAME_WE) add_executable(boost_corosio_doc_${name} ${src}) + boost_corosio_doc_warnings_as_errors(boost_corosio_doc_${name}) target_link_libraries(boost_corosio_doc_${name} PRIVATE Boost::corosio) add_dependencies(tests boost_corosio_doc_${name}) if(name IN_LIST COMPILE_ONLY_PROGRAMS) diff --git a/test/doc/Jamfile b/test/doc/Jamfile index 7e97a6a7b..2741735cf 100644 --- a/test/doc/Jamfile +++ b/test/doc/Jamfile @@ -1,5 +1,6 @@ # # Copyright (c) 2026 Steve Gerbino +# Copyright (c) 2026 Michael Vandeberg # # Distributed under the Boost Software License, Version 1.0. (See accompanying # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -21,7 +22,29 @@ project boost/corosio/test/doc gcc:-Wno-maybe-uninitialized ; -run [ glob snippets/*.cpp ] +# reference/*.cpp holds the examples MrDocs injects into the reference; they are +# compiled here so the b2 legs check them too, not just the CMake ones. +# +# Except under coverage. The examples are never executed, and the coverage build +# passes -fkeep-static-functions (see boost-ci ci/codecov.sh), which keeps the +# unused internal-linkage template instantiations an example pulls in from the +# headers. Those land in include/boost/corosio as never-entered lines and read +# as lost coverage. Today, every coverage leg corosio runs builds with CMake -- +# code-coverage.yml has no b2 step, and ci.yml's b2-workflow step is skipped +# whenever matrix.coverage is set -- so the CMakeLists.txt guard is the one +# actually doing the work. This guard is defensive: it exists so a b2 coverage +# leg added later inherits the same protection without anyone having to +# remember to add it. +import modules ; +local argv = [ modules.peek : ARGV ] ; +local coverage-build = [ MATCH "(--coverage|-fkeep-static-functions)" : $(argv) ] ; +local reference-snippets ; +if ! $(coverage-build) +{ + reference-snippets = [ glob reference/*.cpp ] ; +} + +run [ glob snippets/*.cpp ] $(reference-snippets) ../../../capy/extra/test_suite/test_main.cpp ../../../capy/extra/test_suite/test_suite.cpp : : : ../../../capy/extra/test_suite diff --git a/test/doc/doc_warnings.hpp b/test/doc/doc_warnings.hpp new file mode 100644 index 000000000..d343e8c97 --- /dev/null +++ b/test/doc/doc_warnings.hpp @@ -0,0 +1,55 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +#ifndef BOOST_COROSIO_TEST_DOC_WARNINGS_HPP +#define BOOST_COROSIO_TEST_DOC_WARNINGS_HPP + +/* Warning suppressions shared by every compiled documentation fragment. + + Reference examples deliberately leave results and bindings unused. The + reference explains those values in prose instead, and adding a use would put + code on the page that teaches nothing. test/doc builds with -Wall -Wextra + -Werror (test/doc/CMakeLists.txt and test/doc/Jamfile), so an unused result + would otherwise fail the build. + + Include this first in a fragment, and always outside every tag:: region -- + the site renders those regions, and scaffolding must not appear on a page. + + The pragmas have no push/pop, so they apply to the rest of the translation + unit. That is the intent: a fragment is scaffolding plus tagged regions, and + both need them. + + Keep this list minimal. A warning that fires in one fragment belongs in that + fragment, under a comment saying why, not here. +*/ + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" +#pragma GCC diagnostic ignored "-Wunused-variable" +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wunused-value" +#pragma GCC diagnostic ignored "-Wunused-result" +#pragma GCC diagnostic ignored "-Wunused-function" +#endif +#if defined(__clang__) +#pragma clang diagnostic ignored "-Wunused-lambda-capture" +#pragma clang diagnostic ignored "-Wunused-private-field" +#endif +#if defined(_MSC_VER) +#pragma warning(disable: 4834) // discarding [[nodiscard]] return value +#pragma warning(disable: 4189) // local variable initialized but not referenced +#pragma warning(disable: 4100) // unreferenced formal parameter +#pragma warning(disable: 4101) // unreferenced local variable +#pragma warning(disable: 4456) // declaration hides previous local declaration +#pragma warning(disable: 4457) // declaration hides function parameter +#pragma warning(disable: 4458) // declaration hides class member +#pragma warning(disable: 4459) // declaration hides global declaration +#endif + +#endif diff --git a/test/doc/reference/connect.function.cpp b/test/doc/reference/connect.function.cpp new file mode 100644 index 000000000..5aca864db --- /dev/null +++ b/test/doc/reference/connect.function.cpp @@ -0,0 +1,53 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/connect.hpp's +// documentation for the range overload of connect (the constrained +// function template `template requires std::convertible_to<...>` at connect.hpp:140), by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what +// the reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include +#include +#include + +#include + +#include + +namespace corosio = boost::corosio; +namespace capy = boost::capy; + +namespace { + +// Resolving and connecting to a public hostname needs the network; +// compiled, never run. +// tag::connect[] +capy::task<> connect_to_first_available(corosio::io_context& ioc) +{ + corosio::resolver r(ioc); + auto [rec, results] = co_await r.resolve("www.boost.org", "80"); + if (rec) + co_return; + + corosio::tcp_socket s(ioc); + + // std::move avoids a deep copy of results -- resolver_results owns + // two std::strings per entry, and results is not used again below. + auto [cec, ep] = co_await corosio::connect(s, std::move(results)); + if (cec) + co_return; +} +// end::connect[] + +} // namespace diff --git a/test/doc/reference/delay.function.cpp b/test/doc/reference/delay.function.cpp new file mode 100644 index 000000000..83003e434 --- /dev/null +++ b/test/doc/reference/delay.function.cpp @@ -0,0 +1,60 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/delay.hpp's +// documentation for delay, by doc/addons/extensions/reference-snippets.lua. +// The tagged region is what the reference renders; scaffolding stays outside +// the tags. +// +// Two overloads, two named regions in one file: the duration overload waits +// a fixed span; the Clock overload (shown here with system_clock, since that +// is the case the default wait_traits does not track exactly) waits for an +// arbitrary clock to reach a deadline. Both regions show the same +// cancellation contract: `error::canceled` when the environment's stop +// token wins the race, an empty error_code when the deadline is reached. + +#include "../doc_warnings.hpp" + +#include +#include +#include + +#include +#include + +namespace corosio = boost::corosio; +namespace capy = boost::capy; + +namespace { + +// tag::duration[] +capy::task<> wait_briefly() +{ + auto [ec] = co_await corosio::delay(std::chrono::milliseconds(100)); + if (ec == capy::cond::canceled) + co_return; + if (!ec) + std::cout << "100ms elapsed\n"; +} +// end::duration[] + +// Waits a real wall-clock hour if ever launched; compiled, never run. +// tag::system_clock_deadline[] +capy::task<> wait_one_hour_wall_clock() +{ + auto [ec] = co_await corosio::delay( + std::chrono::system_clock::now() + std::chrono::hours(1)); + if (ec == capy::cond::canceled) + co_return; + if (!ec) + std::cout << "one wall-clock hour elapsed\n"; +} +// end::system_clock_deadline[] + +} // namespace diff --git a/test/doc/reference/endpoint.record.cpp b/test/doc/reference/endpoint.record.cpp new file mode 100644 index 000000000..1fe9ef12c --- /dev/null +++ b/test/doc/reference/endpoint.record.cpp @@ -0,0 +1,44 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/endpoint.hpp's +// documentation for endpoint, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::endpoint[] +void construct_endpoints() +{ + // IPv4 endpoint + corosio::endpoint ep4(corosio::ipv4_address::loopback(), 8080); + + // IPv6 endpoint + corosio::endpoint ep6(corosio::ipv6_address::loopback(), 8080); + + // Port only (defaults to IPv4 any address) + corosio::endpoint bind_addr(8080); + + // Create from string + auto [ec, ep] = corosio::make_endpoint("192.168.1.1:8080"); + if (ec) + return; +} +// end::endpoint[] + +} // namespace diff --git a/test/doc/reference/host_name.function.cpp b/test/doc/reference/host_name.function.cpp new file mode 100644 index 000000000..d43ac5d80 --- /dev/null +++ b/test/doc/reference/host_name.function.cpp @@ -0,0 +1,35 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/host_name.hpp's +// documentation for host_name, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include + +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::host_name[] +void print_local_host_name() +{ + auto [ec, h] = corosio::host_name(); + if (ec) + return; + std::cout << "running on " << h << "\n"; +} +// end::host_name[] + +} // namespace diff --git a/test/doc/reference/io_context.record.cpp b/test/doc/reference/io_context.record.cpp new file mode 100644 index 000000000..f18706293 --- /dev/null +++ b/test/doc/reference/io_context.record.cpp @@ -0,0 +1,41 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/io_context.hpp's +// documentation for io_context, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. +// +// The explicit-backend constructor takes any backend tag value; the example +// names corosio::epoll for concreteness. That tag exists only where the +// platform has epoll (see backend.hpp), so the whole region is guarded -- +// this file is compiled on every CI leg, including macOS and Windows. +// Injection is textual and reads the tag unconditionally, so the guard has +// no effect on the rendered page. + +#include "../doc_warnings.hpp" + +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +#if BOOST_COROSIO_HAS_EPOLL +// tag::construct[] +void construct_contexts() +{ + corosio::io_context ioc; // platform default (epoll on Linux) + corosio::io_context ioc2(corosio::epoll); // explicit backend +} +// end::construct[] +#endif // BOOST_COROSIO_HAS_EPOLL + +} // namespace diff --git a/test/doc/reference/io_context_options.record.cpp b/test/doc/reference/io_context_options.record.cpp new file mode 100644 index 000000000..e654da649 --- /dev/null +++ b/test/doc/reference/io_context_options.record.cpp @@ -0,0 +1,47 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/io_context.hpp's +// documentation for io_context_options, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::configure[] +void configure_for_high_throughput() +{ + corosio::io_context_options opts; + + // Larger epoll_wait()/kevent() batches trade per-connection fairness + // for fewer syscalls under sustained load. + opts.max_events_per_poll = 256; + + // Raises the ceiling on adaptive inline-completion ramp-up: the + // budget still starts small and doubles each fully-consumed cycle, + // capped here instead of at the smaller default. On a multi-threaded + // context (concurrency_hint > 1), touching any budget field like + // this one also opts out of the library's default of disabling + // inline completion entirely for cross-thread work-stealing. + opts.inline_budget_max = 32; + + // More worker threads for blocking file I/O and DNS resolution. + opts.thread_pool_size = 4; + + corosio::io_context ioc(opts); +} +// end::configure[] + +} // namespace diff --git a/test/doc/reference/io_stream.record.cpp b/test/doc/reference/io_stream.record.cpp new file mode 100644 index 000000000..280d59fa8 --- /dev/null +++ b/test/doc/reference/io_stream.record.cpp @@ -0,0 +1,50 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/io/io_stream.hpp's +// documentation for io_stream, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what +// the reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include + +#include +#include +#include + +#include +#include +#include + +namespace corosio = boost::corosio; +namespace capy = boost::capy; + +namespace { + +// tag::io_stream[] +// Read until buffer full or EOF +capy::task<> read_all(corosio::io_stream& stream, std::span buf) +{ + std::size_t total = 0; + while (total < buf.size()) + { + auto [ec, n] = co_await stream.read_some( + capy::mutable_buffer(buf.data() + total, buf.size() - total)); + if (ec == capy::cond::eof) + break; + if (ec) + throw std::system_error(ec); + total += n; + } +} +// end::io_stream[] + +} // namespace diff --git a/test/doc/reference/ipv4_address__to_string.function.cpp b/test/doc/reference/ipv4_address__to_string.function.cpp new file mode 100644 index 000000000..e22d72776 --- /dev/null +++ b/test/doc/reference/ipv4_address__to_string.function.cpp @@ -0,0 +1,32 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/ipv4_address.hpp's +// documentation for ipv4_address::to_string, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include + +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::to_string[] +void print_as_dotted_decimal() +{ + assert(corosio::ipv4_address(0x01020304).to_string() == "1.2.3.4"); +} +// end::to_string[] + +} // namespace diff --git a/test/doc/reference/ipv6_address__to_string.function.cpp b/test/doc/reference/ipv6_address__to_string.function.cpp new file mode 100644 index 000000000..f5dcec09a --- /dev/null +++ b/test/doc/reference/ipv6_address__to_string.function.cpp @@ -0,0 +1,36 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/ipv6_address.hpp's +// documentation for ipv6_address::to_string, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include + +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::to_string[] +void print_as_colon_hex() +{ + corosio::ipv6_address::bytes_type b = {{ + 0, 1, 0, 2, 0, 3, 0, 4, + 0, 5, 0, 6, 0, 7, 0, 8 }}; + corosio::ipv6_address a(b); + assert(a.to_string() == "1:2:3:4:5:6:7:8"); +} +// end::to_string[] + +} // namespace diff --git a/test/doc/reference/local_datagram_socket.record.cpp b/test/doc/reference/local_datagram_socket.record.cpp new file mode 100644 index 000000000..170ffb7f7 --- /dev/null +++ b/test/doc/reference/local_datagram_socket.record.cpp @@ -0,0 +1,67 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into +// include/boost/corosio/local_datagram_socket.hpp's documentation for +// local_datagram_socket, by doc/addons/extensions/reference-snippets.lua. +// The tagged region is what the reference renders; scaffolding stays +// outside the tags. +// +// local_datagram_socket's whole class body is wrapped in +// #if BOOST_COROSIO_POSIX in its own header (Windows has no AF_UNIX +// SOCK_DGRAM support), so the region that names the type is guarded the +// same way, following test/doc/snippets/4p_unix_sockets.cpp. The includes +// below are safe unconditionally -- the header itself resolves to nothing +// off POSIX. + +#include "../doc_warnings.hpp" + +#include +#include +#include +#include + +#include +#include + +namespace corosio = boost::corosio; +namespace capy = boost::capy; + +namespace { + +#if BOOST_COROSIO_POSIX +// tag::connectionless_and_connected[] +capy::task<> connectionless_and_connected(corosio::io_context& ioc) +{ + // Connectionless + corosio::local_datagram_socket sender(ioc); + if (auto ec = sender.open()) + co_return; + if (auto ec = sender.bind(corosio::local_endpoint("/tmp/sender.sock"))) + co_return; + auto [ec, n] = co_await sender.send_to( + capy::const_buffer("hello", 5), + corosio::local_endpoint("/tmp/receiver.sock")); + if (ec) + co_return; + + // Connected + corosio::local_datagram_socket sock(ioc); + auto [cec] = co_await sock.connect(corosio::local_endpoint("/tmp/peer.sock")); + if (cec) + co_return; + auto [ec2, n2] = co_await sock.send( + capy::const_buffer("hi", 2)); + if (ec2) + co_return; +} +// end::connectionless_and_connected[] +#endif // BOOST_COROSIO_POSIX + +} // namespace diff --git a/test/doc/reference/local_stream.record.cpp b/test/doc/reference/local_stream.record.cpp new file mode 100644 index 000000000..a78eb67b2 --- /dev/null +++ b/test/doc/reference/local_stream.record.cpp @@ -0,0 +1,40 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/local_stream.hpp's +// documentation for local_stream, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. +// +// local_stream is a plain protocol tag with no platform-specific member -- +// unlike local_datagram, it is not gated behind BOOST_COROSIO_POSIX in its +// own header, and local_stream_socket ships a Windows (IOCP) backend too +// (native/detail/iocp/win_local_stream_socket.hpp), so this region needs +// no platform guard. + +#include "../doc_warnings.hpp" + +#include +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::open_with_protocol[] +void open_with_protocol(corosio::io_context& ctx) +{ + corosio::local_stream_socket sock(ctx); + if (auto ec = sock.open(corosio::local_stream{})) + return; +} +// end::open_with_protocol[] + +} // namespace diff --git a/test/doc/reference/local_stream_acceptor.record.cpp b/test/doc/reference/local_stream_acceptor.record.cpp new file mode 100644 index 000000000..61a243b5d --- /dev/null +++ b/test/doc/reference/local_stream_acceptor.record.cpp @@ -0,0 +1,53 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into +// include/boost/corosio/local_stream_acceptor.hpp's documentation for +// local_stream_acceptor, by doc/addons/extensions/reference-snippets.lua. +// The tagged region is what the reference renders; scaffolding stays +// outside the tags. +// +// local_stream_acceptor is not gated behind BOOST_COROSIO_POSIX -- it has a +// Windows (IOCP) backend (native/detail/iocp/win_local_stream_acceptor.hpp), +// and bind_option::unlink_existing is handled on both POSIX (::unlink) and +// Windows (::DeleteFileA) in local_stream_acceptor.cpp, so this region needs +// no platform guard. + +#include "../doc_warnings.hpp" + +#include +#include +#include + +#include + +namespace corosio = boost::corosio; +namespace capy = boost::capy; + +namespace { + +// tag::bind_listen_accept[] +capy::task<> bind_listen_accept(corosio::io_context& ioc) +{ + corosio::local_stream_acceptor acc(ioc); + if (auto ec = acc.open()) + co_return; + if (auto ec = acc.bind(corosio::local_endpoint("/tmp/my_app.sock"), + corosio::bind_option::unlink_existing)) + co_return; + if (auto ec = acc.listen()) + co_return; + + auto [aec, peer] = co_await acc.accept(); + if (aec) + co_return; +} +// end::bind_listen_accept[] + +} // namespace diff --git a/test/doc/reference/local_stream_socket.record.cpp b/test/doc/reference/local_stream_socket.record.cpp new file mode 100644 index 000000000..b0fd68e1e --- /dev/null +++ b/test/doc/reference/local_stream_socket.record.cpp @@ -0,0 +1,51 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into +// include/boost/corosio/local_stream_socket.hpp's documentation for +// local_stream_socket, by doc/addons/extensions/reference-snippets.lua. The +// tagged region is what the reference renders; scaffolding stays outside the +// tags. +// +// local_stream_socket is not gated behind BOOST_COROSIO_POSIX -- it has a +// Windows (IOCP) backend (native/detail/iocp/win_local_stream_socket.hpp), +// so this region needs no platform guard. + +#include "../doc_warnings.hpp" + +#include +#include +#include + +#include +#include + +namespace corosio = boost::corosio; +namespace capy = boost::capy; + +namespace { + +// tag::connect_and_read[] +capy::task<> connect_and_read(corosio::io_context& ioc) +{ + corosio::local_stream_socket s(ioc); + + auto [ec] = co_await s.connect(corosio::local_endpoint("/tmp/my.sock")); + if (ec) + co_return; + + char buf[1024]; + auto [read_ec, n] = co_await s.read_some( + capy::mutable_buffer(buf, sizeof(buf))); + if (read_ec) + co_return; +} +// end::connect_and_read[] + +} // namespace diff --git a/test/doc/reference/make_endpoint.function.cpp b/test/doc/reference/make_endpoint.function.cpp new file mode 100644 index 000000000..7a45cd7d1 --- /dev/null +++ b/test/doc/reference/make_endpoint.function.cpp @@ -0,0 +1,40 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/endpoint.hpp's +// documentation for make_endpoint, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include + +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::make_endpoint[] +void parse_v4_and_v6() +{ + auto [ec, ep] = corosio::make_endpoint("192.168.1.1:8080"); + if (ec) + return; + assert(ep.is_v4() && ep.port() == 8080); + + auto [ec6, ep6] = corosio::make_endpoint("[::1]:443"); + if (ec6) + return; + assert(ep6.is_v6() && ep6.port() == 443); +} +// end::make_endpoint[] + +} // namespace diff --git a/test/doc/reference/native_io_context.record.cpp b/test/doc/reference/native_io_context.record.cpp new file mode 100644 index 000000000..84b8a58a7 --- /dev/null +++ b/test/doc/reference/native_io_context.record.cpp @@ -0,0 +1,42 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into +// include/boost/corosio/native/native_io_context.hpp's documentation for +// native_io_context, by doc/addons/extensions/reference-snippets.lua. The +// tagged region is what the reference renders; scaffolding stays outside the +// tags. +// +// native_io_context is a class template (`template`); the +// reference slug drops the template parameter, but the example must still +// name a concrete backend tag. corosio::epoll is what this library actually +// offers as a compile-time tag on Linux (see backend.hpp); other platforms +// get iocp_t/kqueue_t/select_t/io_uring_t instead, so the whole example is +// guarded on the tag it names actually existing. + +#include "../doc_warnings.hpp" + +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +#if BOOST_COROSIO_HAS_EPOLL +// tag::poll[] +void poll_native_context() +{ + corosio::native_io_context ctx; + ctx.poll(); // devirtualized call, no vtable dispatch +} +// end::poll[] +#endif // BOOST_COROSIO_HAS_EPOLL + +} // namespace diff --git a/test/doc/reference/native_local_datagram_socket.record.cpp b/test/doc/reference/native_local_datagram_socket.record.cpp new file mode 100644 index 000000000..fdb612b5d --- /dev/null +++ b/test/doc/reference/native_local_datagram_socket.record.cpp @@ -0,0 +1,61 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into +// include/boost/corosio/native/native_local_datagram_socket.hpp's +// documentation for native_local_datagram_socket, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what +// the reference renders; scaffolding stays outside the tags. +// +// native_local_datagram_socket is itself wrapped in +// #if BOOST_COROSIO_POSIX in its own header (no Windows/IOCP backend -- +// iocp_t has no local_datagram_socket_type in backend.hpp), and the +// reference slug drops the template parameter, so the example must also +// name a concrete backend tag that actually exists. corosio::epoll +// satisfies both constraints at once: it only exists on Linux +// (BOOST_COROSIO_HAS_EPOLL), which is always POSIX, matching +// native_io_context.record.cpp's precedent for this exact class of example. + +#include "../doc_warnings.hpp" + +#include +#include +#include +#include + +#include +#include + +namespace corosio = boost::corosio; +namespace capy = boost::capy; + +namespace { + +#if BOOST_COROSIO_HAS_EPOLL +// tag::open_bind_recv[] +capy::task<> open_bind_recv() +{ + corosio::native_io_context ctx; + corosio::native_local_datagram_socket s(ctx); + if (auto ec = s.open()) + co_return; + if (auto ec = s.bind(corosio::local_endpoint("/tmp/recv.sock"))) + co_return; + + char buf[1024]; + corosio::local_endpoint sender; + auto [ec, n] = co_await s.recv_from( + capy::mutable_buffer(buf, sizeof(buf)), sender); + if (ec) + co_return; +} +// end::open_bind_recv[] +#endif // BOOST_COROSIO_HAS_EPOLL + +} // namespace diff --git a/test/doc/reference/native_local_stream_socket.record.cpp b/test/doc/reference/native_local_stream_socket.record.cpp new file mode 100644 index 000000000..e0b5ec3e7 --- /dev/null +++ b/test/doc/reference/native_local_stream_socket.record.cpp @@ -0,0 +1,52 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into +// include/boost/corosio/native/native_local_stream_socket.hpp's +// documentation for native_local_stream_socket, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what +// the reference renders; scaffolding stays outside the tags. +// +// native_local_stream_socket is a class template (`template`); +// the reference slug drops the template parameter, but the example must +// still name a concrete backend tag. corosio::epoll is what this library +// actually offers as a compile-time tag on Linux (see backend.hpp); every +// backend tag defines local_stream_socket_type, so the type itself is not +// the constraint -- the tag's own existence is. Guarding on +// BOOST_COROSIO_HAS_EPOLL (rather than BOOST_COROSIO_POSIX) matches +// native_io_context.record.cpp's precedent for this exact class of example. + +#include "../doc_warnings.hpp" + +#include +#include +#include +#include + +#include + +namespace corosio = boost::corosio; +namespace capy = boost::capy; + +namespace { + +#if BOOST_COROSIO_HAS_EPOLL +// tag::connect[] +capy::task<> connect_native() +{ + corosio::native_io_context ctx; + corosio::native_local_stream_socket s(ctx); + auto [ec] = co_await s.connect(corosio::local_endpoint("/tmp/my.sock")); + if (ec) + co_return; +} +// end::connect[] +#endif // BOOST_COROSIO_HAS_EPOLL + +} // namespace diff --git a/test/doc/reference/native_random_access_file.record.cpp b/test/doc/reference/native_random_access_file.record.cpp new file mode 100644 index 000000000..95cb26def --- /dev/null +++ b/test/doc/reference/native_random_access_file.record.cpp @@ -0,0 +1,56 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into +// include/boost/corosio/native/native_random_access_file.hpp's +// documentation for native_random_access_file, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what +// the reference renders; scaffolding stays outside the tags. +// +// native_random_access_file is a class template (`template`); +// the reference slug drops the template parameter, but the example must +// still name a concrete backend tag. corosio::epoll is what this library +// actually offers as a compile-time tag on Linux (see backend.hpp), +// matching native_io_context.record.cpp's precedent for this exact class +// of example. + +#include "../doc_warnings.hpp" + +#include +#include +#include +#include + +#include +#include + +namespace corosio = boost::corosio; +namespace capy = boost::capy; + +namespace { + +#if BOOST_COROSIO_HAS_EPOLL +// tag::native_random_access_file[] +capy::task<> open_and_read_at() +{ + corosio::native_io_context ctx; + corosio::native_random_access_file f(ctx); + if (auto ec = f.open("data.bin", corosio::file_base::read_only)) + co_return; + + char buf[4096]; + auto [ec, n] = co_await f.read_some_at( + 0, capy::mutable_buffer(buf, sizeof(buf))); + if (ec) + co_return; +} +// end::native_random_access_file[] +#endif // BOOST_COROSIO_HAS_EPOLL + +} // namespace diff --git a/test/doc/reference/native_socket_option__boolean.record.cpp b/test/doc/reference/native_socket_option__boolean.record.cpp new file mode 100644 index 000000000..7411f93d7 --- /dev/null +++ b/test/doc/reference/native_socket_option__boolean.record.cpp @@ -0,0 +1,48 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into +// include/boost/corosio/native/native_socket_option.hpp's documentation for +// native_socket_option::boolean, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +namespace corosio = boost::corosio; + +namespace { + +// tag::boolean[] +void receive_urgent_data_inline(corosio::tcp_socket& sock) +{ + // corosio has no dedicated type for SO_OOBINLINE; naming the level and + // option as template arguments is what this class is for -- reaching an + // option the library does not wrap, instead of hand-rolling a + // setsockopt() call. + using oob_inline = + corosio::native_socket_option::boolean; + + // A peer's TCP urgent byte normally has to be read separately with + // MSG_OOB; enabling this folds it into the regular byte stream at its + // marked position instead. + sock.set_option(oob_inline(true)); +} +// end::boolean[] + +} // namespace diff --git a/test/doc/reference/native_socket_option__integer.record.cpp b/test/doc/reference/native_socket_option__integer.record.cpp new file mode 100644 index 000000000..33c00f34d --- /dev/null +++ b/test/doc/reference/native_socket_option__integer.record.cpp @@ -0,0 +1,50 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into +// include/boost/corosio/native/native_socket_option.hpp's documentation for +// native_socket_option::integer, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include + +#ifdef _WIN32 +#include +#include +#else +#include +#endif + +namespace corosio = boost::corosio; + +namespace { + +// tag::integer[] +void limit_how_far_outgoing_packets_can_travel(corosio::udp_socket& sock) +{ + // Precondition: sock is open on udp::v4(). IPPROTO_IP options don't + // apply to an AF_INET6 socket; set_option compiles either way and + // throws at runtime on the wrong family. + // + // corosio wraps the multicast hop limit (multicast_hops_v4) but not the + // plain unicast one; IP_TTL is the general-purpose option this class is + // for. A low value keeps a datagram from leaving the local network even + // when a route to a farther destination exists. + using unicast_ttl_v4 = + corosio::native_socket_option::integer; + + sock.set_option(unicast_ttl_v4(1)); +} +// end::integer[] + +} // namespace diff --git a/test/doc/reference/native_socket_option__join_group_v4.record.cpp b/test/doc/reference/native_socket_option__join_group_v4.record.cpp new file mode 100644 index 000000000..60e66430b --- /dev/null +++ b/test/doc/reference/native_socket_option__join_group_v4.record.cpp @@ -0,0 +1,56 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into +// include/boost/corosio/native/native_socket_option.hpp's documentation for +// native_socket_option::join_group_v4, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include +#include +#include +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::join_group_v4[] +void receive_an_ipv4_multicast_group(corosio::io_context& ioc) +{ + corosio::udp_socket sock(ioc); + if (auto ec = sock.open(corosio::udp::v4())) + return; // report the error + + // Lets other listeners on this host bind the same port and receive the + // same group. set_option reports failure by throwing, not by returning + // a code. + sock.set_option(corosio::native_socket_option::reuse_address(true)); + + // Bind before joining: a membership attaches to the socket's local port, + // so there is nothing for the join to attach to until the bind succeeds. + if (auto ec = sock.bind( + corosio::endpoint(corosio::ipv4_address::any(), 9000))) + return; // report the error + + // 239.0.0.0/8 is the administratively scoped range, the IPv4 counterpart + // of a private address range. The optional second argument names the + // local interface to receive on; the default, 0.0.0.0, lets the kernel + // choose one. + sock.set_option(corosio::native_socket_option::join_group_v4( + corosio::ipv4_address("239.255.0.1"))); +} +// end::join_group_v4[] + +} // namespace diff --git a/test/doc/reference/native_socket_option__join_group_v6.record.cpp b/test/doc/reference/native_socket_option__join_group_v6.record.cpp new file mode 100644 index 000000000..b14cb9923 --- /dev/null +++ b/test/doc/reference/native_socket_option__join_group_v6.record.cpp @@ -0,0 +1,56 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into +// include/boost/corosio/native/native_socket_option.hpp's documentation for +// native_socket_option::join_group_v6, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include +#include +#include +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::join_group_v6[] +void receive_an_ipv6_multicast_group(corosio::io_context& ioc) +{ + corosio::udp_socket sock(ioc); + if (auto ec = sock.open(corosio::udp::v6())) + return; // report the error + + // Lets other listeners on this host bind the same port and receive the + // same group. set_option reports failure by throwing, not by returning + // a code. + sock.set_option(corosio::native_socket_option::reuse_address(true)); + + // Bind before joining: a membership attaches to the socket's local port, + // so there is nothing for the join to attach to until the bind succeeds. + if (auto ec = sock.bind( + corosio::endpoint(corosio::ipv6_address::any(), 9000))) + return; // report the error + + // ff15::1234 is a transient, site-scoped group: the 1 marks it + // non-permanent, the 5 sets the scope. The interface index selects which + // link to join on; 0 lets the kernel choose, and if_nametoindex() maps a + // name such as "eth0". + sock.set_option(corosio::native_socket_option::join_group_v6( + corosio::ipv6_address("ff15::1234"), 0)); +} +// end::join_group_v6[] + +} // namespace diff --git a/test/doc/reference/native_socket_option__leave_group_v4.record.cpp b/test/doc/reference/native_socket_option__leave_group_v4.record.cpp new file mode 100644 index 000000000..1769191ec --- /dev/null +++ b/test/doc/reference/native_socket_option__leave_group_v4.record.cpp @@ -0,0 +1,41 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into +// include/boost/corosio/native/native_socket_option.hpp's documentation for +// native_socket_option::leave_group_v4, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::leave_group_v4[] +void stop_receiving_an_ipv4_multicast_group(corosio::udp_socket& sock) +{ + // Precondition: sock is open on udp::v4() and joined this group. + // + // Membership otherwise lasts until the socket closes. The group and the + // interface have to match the join_group_v4 that established it -- + // attempting to leave a (group, interface) pair the kernel has no + // membership for fails with EADDRNOTAVAIL, which set_option reports by + // throwing. + sock.set_option(corosio::native_socket_option::leave_group_v4( + corosio::ipv4_address("239.255.0.1"))); +} +// end::leave_group_v4[] + +} // namespace diff --git a/test/doc/reference/native_socket_option__leave_group_v6.record.cpp b/test/doc/reference/native_socket_option__leave_group_v6.record.cpp new file mode 100644 index 000000000..1531b8d66 --- /dev/null +++ b/test/doc/reference/native_socket_option__leave_group_v6.record.cpp @@ -0,0 +1,41 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into +// include/boost/corosio/native/native_socket_option.hpp's documentation for +// native_socket_option::leave_group_v6, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::leave_group_v6[] +void stop_receiving_an_ipv6_multicast_group(corosio::udp_socket& sock) +{ + // Precondition: sock is open on udp::v6() and joined this group. + // + // Membership otherwise lasts until the socket closes. The group and the + // interface index have to match the join_group_v6 that established it -- + // attempting to leave a (group, interface) pair the kernel has no + // membership for fails with EADDRNOTAVAIL, which set_option reports by + // throwing. + sock.set_option(corosio::native_socket_option::leave_group_v6( + corosio::ipv6_address("ff15::1234"), 0)); +} +// end::leave_group_v6[] + +} // namespace diff --git a/test/doc/reference/native_socket_option__linger.record.cpp b/test/doc/reference/native_socket_option__linger.record.cpp new file mode 100644 index 000000000..3511774bb --- /dev/null +++ b/test/doc/reference/native_socket_option__linger.record.cpp @@ -0,0 +1,46 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into +// include/boost/corosio/native/native_socket_option.hpp's documentation for +// native_socket_option::linger, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::linger[] +void control_what_close_does_with_queued_data(corosio::tcp_socket& sock) +{ + // A non-zero timeout can make close() block the calling thread for up + // to that many seconds. close() also runs from the destructor and from + // move-assignment, and in an async program the thread reaching those is + // usually the one running the event loop -- so weigh this option + // against stalling that loop. A zero timeout means the opposite: + // close() discards whatever is queued and sends an RST. + // + // This native variant stores the platform's struct linger directly, + // where socket_option::linger keeps the same bytes behind opaque + // storage -- an implementation detail invisible at this call site. + sock.set_option(corosio::native_socket_option::linger(true, 5)); + + auto opt = sock.get_option(); + bool waits = opt.enabled(); + int seconds = opt.timeout(); +} +// end::linger[] + +} // namespace diff --git a/test/doc/reference/native_socket_option__multicast_interface_v4.record.cpp b/test/doc/reference/native_socket_option__multicast_interface_v4.record.cpp new file mode 100644 index 000000000..ef205ffe4 --- /dev/null +++ b/test/doc/reference/native_socket_option__multicast_interface_v4.record.cpp @@ -0,0 +1,40 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into +// include/boost/corosio/native/native_socket_option.hpp's documentation for +// native_socket_option::multicast_interface_v4, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::multicast_interface_v4[] +void choose_the_outgoing_interface_v4(corosio::udp_socket& sock) +{ + // Precondition: sock is open on udp::v4(). + // + // IPv4 names an interface by a local address bound to it, where + // multicast_interface_v6 takes an interface index. The default, + // 0.0.0.0, leaves the choice to the routing table -- which on a + // multi-homed host is rarely the interface you meant. + sock.set_option(corosio::native_socket_option::multicast_interface_v4( + corosio::ipv4_address("192.168.1.1"))); +} +// end::multicast_interface_v4[] + +} // namespace diff --git a/test/doc/reference/native_stream_file.record.cpp b/test/doc/reference/native_stream_file.record.cpp new file mode 100644 index 000000000..4a495c5cd --- /dev/null +++ b/test/doc/reference/native_stream_file.record.cpp @@ -0,0 +1,55 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into +// include/boost/corosio/native/native_stream_file.hpp's documentation for +// native_stream_file, by doc/addons/extensions/reference-snippets.lua. The +// tagged region is what the reference renders; scaffolding stays outside +// the tags. +// +// native_stream_file is a class template (`template`); the +// reference slug drops the template parameter, but the example must still +// name a concrete backend tag. corosio::epoll is what this library actually +// offers as a compile-time tag on Linux (see backend.hpp), matching +// native_io_context.record.cpp's precedent for this exact class of example. + +#include "../doc_warnings.hpp" + +#include +#include +#include +#include + +#include +#include + +namespace corosio = boost::corosio; +namespace capy = boost::capy; + +namespace { + +#if BOOST_COROSIO_HAS_EPOLL +// tag::native_stream_file[] +capy::task<> open_and_read() +{ + corosio::native_io_context ctx; + corosio::native_stream_file f(ctx); + if (auto ec = f.open("data.bin", corosio::file_base::read_only)) + co_return; + + char buf[4096]; + auto [ec, n] = co_await f.read_some( + capy::mutable_buffer(buf, sizeof(buf))); + if (ec) + co_return; +} +// end::native_stream_file[] +#endif // BOOST_COROSIO_HAS_EPOLL + +} // namespace diff --git a/test/doc/reference/native_tcp_socket.record.cpp b/test/doc/reference/native_tcp_socket.record.cpp new file mode 100644 index 000000000..e34fca9da --- /dev/null +++ b/test/doc/reference/native_tcp_socket.record.cpp @@ -0,0 +1,58 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into +// include/boost/corosio/native/native_tcp_socket.hpp's documentation for +// native_tcp_socket, by doc/addons/extensions/reference-snippets.lua. The +// tagged region is what the reference renders; scaffolding stays outside +// the tags. +// +// native_tcp_socket is a class template (`template`); the +// reference slug drops the template parameter, but the example must still +// name a concrete backend tag. corosio::epoll is what this library actually +// offers as a compile-time tag on Linux (see backend.hpp), matching +// native_io_context.record.cpp's precedent for this exact class of example. + +#include "../doc_warnings.hpp" + +#include +#include +#include +#include +#include + +#include +#include + +namespace corosio = boost::corosio; +namespace capy = boost::capy; + +namespace { + +#if BOOST_COROSIO_HAS_EPOLL +// tag::native_tcp_socket[] +capy::task<> connect_and_read() +{ + corosio::native_io_context ctx; + corosio::native_tcp_socket s(ctx); + auto [ec] = co_await s.connect( + corosio::endpoint(corosio::ipv4_address::loopback(), 8080)); + if (ec) + co_return; + + char buf[1024]; + auto [ec2, n] = co_await s.read_some( + capy::mutable_buffer(buf, sizeof(buf))); + if (ec2) + co_return; +} +// end::native_tcp_socket[] +#endif // BOOST_COROSIO_HAS_EPOLL + +} // namespace diff --git a/test/doc/reference/native_udp_socket.record.cpp b/test/doc/reference/native_udp_socket.record.cpp new file mode 100644 index 000000000..0c8ae4fa1 --- /dev/null +++ b/test/doc/reference/native_udp_socket.record.cpp @@ -0,0 +1,60 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into +// include/boost/corosio/native/native_udp_socket.hpp's documentation for +// native_udp_socket, by doc/addons/extensions/reference-snippets.lua. The +// tagged region is what the reference renders; scaffolding stays outside +// the tags. +// +// native_udp_socket is a class template (`template`); the +// reference slug drops the template parameter, but the example must still +// name a concrete backend tag. corosio::epoll is what this library actually +// offers as a compile-time tag on Linux (see backend.hpp), matching +// native_io_context.record.cpp's precedent for this exact class of example. + +#include "../doc_warnings.hpp" + +#include +#include +#include +#include +#include + +#include +#include + +namespace corosio = boost::corosio; +namespace capy = boost::capy; + +namespace { + +#if BOOST_COROSIO_HAS_EPOLL +// tag::native_udp_socket[] +capy::task<> open_bind_recv() +{ + corosio::native_io_context ctx; + corosio::native_udp_socket s(ctx); + if (auto ec = s.open()) + co_return; + if (auto ec = s.bind( + corosio::endpoint(corosio::ipv4_address::any(), 9000))) + co_return; + + char buf[1024]; + corosio::endpoint sender; + auto [ec, n] = co_await s.recv_from( + capy::mutable_buffer(buf, sizeof(buf)), sender); + if (ec) + co_return; +} +// end::native_udp_socket[] +#endif // BOOST_COROSIO_HAS_EPOLL + +} // namespace diff --git a/test/doc/reference/openssl_stream.record.cpp b/test/doc/reference/openssl_stream.record.cpp new file mode 100644 index 000000000..f8d801d83 --- /dev/null +++ b/test/doc/reference/openssl_stream.record.cpp @@ -0,0 +1,73 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/openssl_stream.hpp's +// documentation for openssl_stream, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what +// the reference renders; scaffolding stays outside the tags. +// +// Guarded on BOOST_COROSIO_HAS_OPENSSL, outside the tag, for the same +// reason wolfssl_stream.record.cpp guards on BOOST_COROSIO_HAS_WOLFSSL -- +// see test/doc/CMakeLists.txt's OpenSSL_FOUND block for why. + +#include "../doc_warnings.hpp" + +#if defined(BOOST_COROSIO_HAS_OPENSSL) +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace corosio = boost::corosio; +namespace capy = boost::capy; + +namespace { + +// tag::openssl_stream[] +// Two independently connected sockets demonstrate the two construction +// modes; reusing one socket for both would leave tls pointing at sock +// after it was gutted by the move into tls2 (use-after-move), not a +// dangling reference -- sock itself stays in scope. +capy::task<> reference_and_owning_construction( + corosio::io_context& ioc, corosio::endpoint ep) +{ + corosio::tls_context ctx; + if (auto ec = ctx.set_default_verify_paths()) // trust the system CAs + co_return; + if (auto ec = ctx.set_verify_mode(corosio::tls_verify_mode::peer)) + co_return; + + // Reference mode - sock must outlive tls + corosio::tcp_socket sock(ioc); + auto [ec] = co_await sock.connect(ep); + if (ec) + co_return; + corosio::openssl_stream tls(&sock, ctx); + tls.set_hostname("example.com"); + auto [hec] = co_await tls.handshake(corosio::tls_role::client); + if (hec) + co_return; + + // Or owning mode - tls2 takes ownership of its own connected socket + corosio::tcp_socket sock2(ioc); + auto [ec2] = co_await sock2.connect(ep); + if (ec2) + co_return; + corosio::openssl_stream tls2(std::move(sock2), ctx); +} +// end::openssl_stream[] + +} // namespace +#endif // BOOST_COROSIO_HAS_OPENSSL diff --git a/test/doc/reference/random_access_file.record.cpp b/test/doc/reference/random_access_file.record.cpp new file mode 100644 index 000000000..28748883c --- /dev/null +++ b/test/doc/reference/random_access_file.record.cpp @@ -0,0 +1,47 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into +// include/boost/corosio/random_access_file.hpp's documentation for +// random_access_file, by doc/addons/extensions/reference-snippets.lua. The +// tagged region is what the reference renders; scaffolding stays outside +// the tags. + +#include "../doc_warnings.hpp" + +#include +#include +#include + +#include +#include + +namespace corosio = boost::corosio; +namespace capy = boost::capy; + +namespace { + +// tag::random_access_file[] +// Every read/write names an explicit byte offset; there is no implicit +// position to advance, unlike stream_file. +capy::task<> read_a_file_at_an_offset(corosio::io_context& ioc) +{ + corosio::random_access_file f(ioc); + if (auto ec = f.open("data.bin", corosio::file_base::read_only)) + co_return; // report the error + + char buf[4096]; + auto [ec, n] = co_await f.read_some_at( + 0, capy::mutable_buffer(buf, sizeof(buf))); + if (ec) + co_return; +} +// end::random_access_file[] + +} // namespace diff --git a/test/doc/reference/resolver.record.cpp b/test/doc/reference/resolver.record.cpp new file mode 100644 index 000000000..d252a1a8f --- /dev/null +++ b/test/doc/reference/resolver.record.cpp @@ -0,0 +1,51 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/resolver.hpp's +// documentation for resolver, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include + +#include + +#include +#include + +namespace corosio = boost::corosio; +namespace capy = boost::capy; + +namespace { + +// Resolving a public hostname needs the network; compiled, never run. +// tag::resolver[] +capy::task<> resolve_and_print(corosio::io_context& ioc) +{ + corosio::resolver r(ioc); + + // Using structured bindings + auto [ec, results] = co_await r.resolve("www.example.com", "https"); + if (ec) + co_return; + + for (auto const& entry : results) + std::cout << entry.get_endpoint().port() << std::endl; + + // Or, to convert errors into exceptions: + auto [ec2, results2] = co_await r.resolve("www.example.com", "https"); + if (ec2) + throw std::system_error(ec2); +} +// end::resolver[] + +} // namespace diff --git a/test/doc/reference/resolver__resolve.function.cpp b/test/doc/reference/resolver__resolve.function.cpp new file mode 100644 index 000000000..4798d8e7b --- /dev/null +++ b/test/doc/reference/resolver__resolve.function.cpp @@ -0,0 +1,58 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/resolver.hpp's +// documentation for resolver::resolve, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. +// +// Two overloads, two named regions in one file: `resolve(host, service)` +// performs forward DNS resolution of a name into candidate endpoints +// (forward_resolve); `resolve(endpoint const&)` performs reverse DNS +// resolution of an endpoint into a hostname and service name +// (reverse_resolve). Both share the slug `resolver__resolve.function` -- +// naming each region keeps the marker bound to its own overload +// regardless of visit order. + +#include "../doc_warnings.hpp" + +#include +#include +#include + +#include + +#include + +namespace corosio = boost::corosio; +namespace capy = boost::capy; + +namespace { + +// Resolving a public hostname needs the network; compiled, never run. +// tag::forward_resolve[] +capy::task<> forward_resolve(corosio::resolver& r) +{ + auto [ec, results] = co_await r.resolve("www.example.com", "https"); + if (ec) + co_return; +} +// end::forward_resolve[] + +// tag::reverse_resolve[] +capy::task<> reverse_resolve(corosio::resolver& r) +{ + corosio::endpoint ep(corosio::ipv4_address({127, 0, 0, 1}), 80); + auto [ec, result] = co_await r.resolve(ep); + if (!ec) + std::cout << result.host_name() << ":" << result.service_name(); +} +// end::reverse_resolve[] + +} // namespace diff --git a/test/doc/reference/signal_set.record.cpp b/test/doc/reference/signal_set.record.cpp new file mode 100644 index 000000000..5d21656da --- /dev/null +++ b/test/doc/reference/signal_set.record.cpp @@ -0,0 +1,44 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/signal_set.hpp's +// documentation for signal_set, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include +#include +#include + +#include +#include + +namespace corosio = boost::corosio; +namespace capy = boost::capy; + +namespace { + +// Waits for a real SIGINT/SIGTERM if ever launched; compiled, never run. +// tag::wait_for_shutdown[] +capy::task<> wait_for_shutdown(corosio::io_context& ctx) +{ + corosio::signal_set signals(ctx, SIGINT, SIGTERM); + + auto [ec, signum] = co_await signals.wait(); + if (ec == capy::cond::canceled) + co_return; + if (!ec) + std::cout << "Received signal " << signum << "\n"; +} +// end::wait_for_shutdown[] + +} // namespace diff --git a/test/doc/reference/socket_option__broadcast.record.cpp b/test/doc/reference/socket_option__broadcast.record.cpp new file mode 100644 index 000000000..cf5861bac --- /dev/null +++ b/test/doc/reference/socket_option__broadcast.record.cpp @@ -0,0 +1,43 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/socket_option.hpp's +// documentation for socket_option::broadcast, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::broadcast[] +void allow_sending_to_a_broadcast_address(corosio::io_context& ioc) +{ + corosio::udp_socket sock(ioc); + if (auto ec = sock.open(corosio::udp::v4())) + return; // report the error + + // Without this the kernel refuses a send_to a broadcast address; the + // permission is opt-in so a stray destination cannot flood a segment. + // set_option reports failure by throwing, not by returning a code. + sock.set_option(corosio::socket_option::broadcast(true)); + + // send_to may now target ipv4_address::broadcast(), 255.255.255.255, + // or a subnet-directed broadcast address. +} +// end::broadcast[] + +} // namespace diff --git a/test/doc/reference/socket_option__join_group_v4.record.cpp b/test/doc/reference/socket_option__join_group_v4.record.cpp new file mode 100644 index 000000000..ea385141a --- /dev/null +++ b/test/doc/reference/socket_option__join_group_v4.record.cpp @@ -0,0 +1,55 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/socket_option.hpp's +// documentation for socket_option::join_group_v4, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include +#include +#include +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::join_group_v4[] +void receive_an_ipv4_multicast_group(corosio::io_context& ioc) +{ + corosio::udp_socket sock(ioc); + if (auto ec = sock.open(corosio::udp::v4())) + return; // report the error + + // Lets other listeners on this host bind the same port and receive the + // same group. set_option reports failure by throwing, not by returning + // a code. + sock.set_option(corosio::socket_option::reuse_address(true)); + + // Bind before joining: a membership attaches to the socket's local port, + // so there is nothing for the join to attach to until the bind succeeds. + if (auto ec = sock.bind( + corosio::endpoint(corosio::ipv4_address::any(), 9000))) + return; // report the error + + // 239.0.0.0/8 is the administratively scoped range, the IPv4 counterpart + // of a private address range. The optional second argument names the + // local interface to receive on; the default, 0.0.0.0, lets the kernel + // choose one. + sock.set_option(corosio::socket_option::join_group_v4( + corosio::ipv4_address("239.255.0.1"))); +} +// end::join_group_v4[] + +} // namespace diff --git a/test/doc/reference/socket_option__join_group_v6.record.cpp b/test/doc/reference/socket_option__join_group_v6.record.cpp new file mode 100644 index 000000000..b0e1a12cc --- /dev/null +++ b/test/doc/reference/socket_option__join_group_v6.record.cpp @@ -0,0 +1,55 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/socket_option.hpp's +// documentation for socket_option::join_group_v6, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include +#include +#include +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::join_group_v6[] +void receive_an_ipv6_multicast_group(corosio::io_context& ioc) +{ + corosio::udp_socket sock(ioc); + if (auto ec = sock.open(corosio::udp::v6())) + return; // report the error + + // Lets other listeners on this host bind the same port and receive the + // same group. set_option reports failure by throwing, not by returning + // a code. + sock.set_option(corosio::socket_option::reuse_address(true)); + + // Bind before joining: a membership attaches to the socket's local port, + // so there is nothing for the join to attach to until the bind succeeds. + if (auto ec = sock.bind( + corosio::endpoint(corosio::ipv6_address::any(), 9000))) + return; // report the error + + // ff15::1234 is a transient, site-scoped group: the 1 marks it + // non-permanent, the 5 sets the scope. The interface index selects which + // link to join on; 0 lets the kernel choose, and if_nametoindex() maps a + // name such as "eth0". + sock.set_option(corosio::socket_option::join_group_v6( + corosio::ipv6_address("ff15::1234"), 0)); +} +// end::join_group_v6[] + +} // namespace diff --git a/test/doc/reference/socket_option__keep_alive.record.cpp b/test/doc/reference/socket_option__keep_alive.record.cpp new file mode 100644 index 000000000..1faa2b7f6 --- /dev/null +++ b/test/doc/reference/socket_option__keep_alive.record.cpp @@ -0,0 +1,33 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/socket_option.hpp's +// documentation for socket_option::keep_alive, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::keep_alive[] +void detect_a_peer_that_went_away(corosio::tcp_socket& sock) +{ + // Probe an idle connection so a peer that vanished without closing is + // eventually reported as an error instead of hanging forever. + sock.set_option(corosio::socket_option::keep_alive(true)); +} +// end::keep_alive[] + +} // namespace diff --git a/test/doc/reference/socket_option__leave_group_v4.record.cpp b/test/doc/reference/socket_option__leave_group_v4.record.cpp new file mode 100644 index 000000000..7feb27c64 --- /dev/null +++ b/test/doc/reference/socket_option__leave_group_v4.record.cpp @@ -0,0 +1,40 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/socket_option.hpp's +// documentation for socket_option::leave_group_v4, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::leave_group_v4[] +void stop_receiving_an_ipv4_multicast_group(corosio::udp_socket& sock) +{ + // Precondition: sock is open on udp::v4() and joined this group. + // + // Membership otherwise lasts until the socket closes. The group and the + // interface have to match the join_group_v4 that established it -- + // attempting to leave a (group, interface) pair the kernel has no + // membership for fails with EADDRNOTAVAIL, which set_option reports by + // throwing. + sock.set_option(corosio::socket_option::leave_group_v4( + corosio::ipv4_address("239.255.0.1"))); +} +// end::leave_group_v4[] + +} // namespace diff --git a/test/doc/reference/socket_option__leave_group_v6.record.cpp b/test/doc/reference/socket_option__leave_group_v6.record.cpp new file mode 100644 index 000000000..727b5ae0d --- /dev/null +++ b/test/doc/reference/socket_option__leave_group_v6.record.cpp @@ -0,0 +1,40 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/socket_option.hpp's +// documentation for socket_option::leave_group_v6, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::leave_group_v6[] +void stop_receiving_an_ipv6_multicast_group(corosio::udp_socket& sock) +{ + // Precondition: sock is open on udp::v6() and joined this group. + // + // Membership otherwise lasts until the socket closes. The group and the + // interface index have to match the join_group_v6 that established it -- + // attempting to leave a (group, interface) pair the kernel has no + // membership for fails with EADDRNOTAVAIL, which set_option reports by + // throwing. + sock.set_option(corosio::socket_option::leave_group_v6( + corosio::ipv6_address("ff15::1234"), 0)); +} +// end::leave_group_v6[] + +} // namespace diff --git a/test/doc/reference/socket_option__linger.record.cpp b/test/doc/reference/socket_option__linger.record.cpp new file mode 100644 index 000000000..5412d8f4c --- /dev/null +++ b/test/doc/reference/socket_option__linger.record.cpp @@ -0,0 +1,41 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/socket_option.hpp's +// documentation for socket_option::linger, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::linger[] +void control_what_close_does_with_queued_data(corosio::tcp_socket& sock) +{ + // A non-zero timeout can make close() block the calling thread for up + // to that many seconds. close() also runs from the destructor and from + // move-assignment, and in an async program the thread reaching those is + // usually the one running the event loop -- so weigh this option + // against stalling that loop. A zero timeout means the opposite: + // close() discards whatever is queued and sends an RST. + sock.set_option(corosio::socket_option::linger(true, 5)); + + auto opt = sock.get_option(); + bool waits = opt.enabled(); + int seconds = opt.timeout(); +} +// end::linger[] + +} // namespace diff --git a/test/doc/reference/socket_option__multicast_hops_v4.record.cpp b/test/doc/reference/socket_option__multicast_hops_v4.record.cpp new file mode 100644 index 000000000..3a8506fbf --- /dev/null +++ b/test/doc/reference/socket_option__multicast_hops_v4.record.cpp @@ -0,0 +1,37 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/socket_option.hpp's +// documentation for socket_option::multicast_hops_v4, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::multicast_hops_v4[] +void limit_how_far_multicast_travels_v4(corosio::udp_socket& sock) +{ + // Precondition: sock is open on udp::v4(). + // + // The TTL is a router budget, not a distance: 1 (the default) keeps the + // datagram on the local link, 4 lets it cross four routers. Group + // addresses carry an administrative scope of their own -- 239.0.0.0/8 is + // the scoped range -- and a datagram has to satisfy both. + sock.set_option(corosio::socket_option::multicast_hops_v4(4)); +} +// end::multicast_hops_v4[] + +} // namespace diff --git a/test/doc/reference/socket_option__multicast_hops_v6.record.cpp b/test/doc/reference/socket_option__multicast_hops_v6.record.cpp new file mode 100644 index 000000000..df26c850a --- /dev/null +++ b/test/doc/reference/socket_option__multicast_hops_v6.record.cpp @@ -0,0 +1,36 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/socket_option.hpp's +// documentation for socket_option::multicast_hops_v6, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::multicast_hops_v6[] +void limit_how_far_multicast_travels_v6(corosio::udp_socket& sock) +{ + // Precondition: sock is open on udp::v6(). + // + // The hop limit is a router budget: 1 (the default) keeps the datagram + // on the local link, 4 lets it cross four routers. IPv6 also encodes a + // scope in the group address itself, and a datagram has to satisfy both. + sock.set_option(corosio::socket_option::multicast_hops_v6(4)); +} +// end::multicast_hops_v6[] + +} // namespace diff --git a/test/doc/reference/socket_option__multicast_interface_v4.record.cpp b/test/doc/reference/socket_option__multicast_interface_v4.record.cpp new file mode 100644 index 000000000..7c19409e8 --- /dev/null +++ b/test/doc/reference/socket_option__multicast_interface_v4.record.cpp @@ -0,0 +1,39 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/socket_option.hpp's +// documentation for socket_option::multicast_interface_v4, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::multicast_interface_v4[] +void choose_the_outgoing_interface_v4(corosio::udp_socket& sock) +{ + // Precondition: sock is open on udp::v4(). + // + // IPv4 names an interface by a local address bound to it, where + // multicast_interface_v6 takes an interface index. The default, + // 0.0.0.0, leaves the choice to the routing table -- which on a + // multi-homed host is rarely the interface you meant. + sock.set_option(corosio::socket_option::multicast_interface_v4( + corosio::ipv4_address("192.168.1.1"))); +} +// end::multicast_interface_v4[] + +} // namespace diff --git a/test/doc/reference/socket_option__multicast_interface_v6.record.cpp b/test/doc/reference/socket_option__multicast_interface_v6.record.cpp new file mode 100644 index 000000000..945d7af33 --- /dev/null +++ b/test/doc/reference/socket_option__multicast_interface_v6.record.cpp @@ -0,0 +1,39 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/socket_option.hpp's +// documentation for socket_option::multicast_interface_v6, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::multicast_interface_v6[] +void choose_the_outgoing_interface_v6( + corosio::udp_socket& sock, unsigned int if_index) +{ + // Precondition: sock is open on udp::v6(), and if_index is what + // if_nametoindex("eth0") returned for the interface you want. + // + // IPv6 names an interface by index, where multicast_interface_v4 takes a + // local address. Zero, the default, leaves the choice to the routing + // table -- which on a multi-homed host is rarely the interface you meant. + sock.set_option(corosio::socket_option::multicast_interface_v6( + static_cast(if_index))); +} +// end::multicast_interface_v6[] + +} // namespace diff --git a/test/doc/reference/socket_option__multicast_loop_v4.record.cpp b/test/doc/reference/socket_option__multicast_loop_v4.record.cpp new file mode 100644 index 000000000..759dc10dc --- /dev/null +++ b/test/doc/reference/socket_option__multicast_loop_v4.record.cpp @@ -0,0 +1,36 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/socket_option.hpp's +// documentation for socket_option::multicast_loop_v4, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::multicast_loop_v4[] +void loop_multicast_back_to_this_host_v4(corosio::udp_socket& sock) +{ + // Precondition: sock is open on udp::v4(). + // + // Enabled, datagrams this socket sends are also delivered to members of + // the group on this same host, the sending process included. Disable it + // when a sender must not receive its own traffic. + sock.set_option(corosio::socket_option::multicast_loop_v4(true)); +} +// end::multicast_loop_v4[] + +} // namespace diff --git a/test/doc/reference/socket_option__multicast_loop_v6.record.cpp b/test/doc/reference/socket_option__multicast_loop_v6.record.cpp new file mode 100644 index 000000000..337aee6ea --- /dev/null +++ b/test/doc/reference/socket_option__multicast_loop_v6.record.cpp @@ -0,0 +1,36 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/socket_option.hpp's +// documentation for socket_option::multicast_loop_v6, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::multicast_loop_v6[] +void loop_multicast_back_to_this_host_v6(corosio::udp_socket& sock) +{ + // Precondition: sock is open on udp::v6(). + // + // Enabled, datagrams this socket sends are also delivered to members of + // the group on this same host, the sending process included. Disable it + // when a sender must not receive its own traffic. + sock.set_option(corosio::socket_option::multicast_loop_v6(true)); +} +// end::multicast_loop_v6[] + +} // namespace diff --git a/test/doc/reference/socket_option__no_delay.record.cpp b/test/doc/reference/socket_option__no_delay.record.cpp new file mode 100644 index 000000000..33ebe9872 --- /dev/null +++ b/test/doc/reference/socket_option__no_delay.record.cpp @@ -0,0 +1,40 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/socket_option.hpp's +// documentation for socket_option::no_delay, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. +// +// disable_nagle_on_a_connected_socket is also the sentinel that +// .github/workflows/docs.yml greps for in the rendered HTML. It exists here and +// nowhere else, which is what makes its absence from the site mean "the +// transform did not run". Renaming it means updating that gate. + +#include "../doc_warnings.hpp" + +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::no_delay[] +void disable_nagle_on_a_connected_socket(corosio::tcp_socket& sock) +{ + // Send small writes immediately instead of coalescing them. + sock.set_option(corosio::socket_option::no_delay(true)); + + auto nd = sock.get_option(); + bool disabled = nd.value(); // true: Nagle's algorithm is off +} +// end::no_delay[] + +} // namespace diff --git a/test/doc/reference/socket_option__receive_buffer_size.record.cpp b/test/doc/reference/socket_option__receive_buffer_size.record.cpp new file mode 100644 index 000000000..84a33616a --- /dev/null +++ b/test/doc/reference/socket_option__receive_buffer_size.record.cpp @@ -0,0 +1,36 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/socket_option.hpp's +// documentation for socket_option::receive_buffer_size, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::receive_buffer_size[] +void widen_the_receive_buffer(corosio::tcp_socket& sock) +{ + sock.set_option(corosio::socket_option::receive_buffer_size(65536)); + + // The kernel is free to round the request up or clamp it, so read the + // option back rather than assuming the value took effect verbatim. + auto opt = sock.get_option(); + int sz = opt.value(); +} +// end::receive_buffer_size[] + +} // namespace diff --git a/test/doc/reference/socket_option__reuse_address.record.cpp b/test/doc/reference/socket_option__reuse_address.record.cpp new file mode 100644 index 000000000..2d1f473e3 --- /dev/null +++ b/test/doc/reference/socket_option__reuse_address.record.cpp @@ -0,0 +1,46 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/socket_option.hpp's +// documentation for socket_option::reuse_address, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::reuse_address[] +void restart_a_listener_on_the_same_port(corosio::tcp_acceptor& acc) +{ + if (auto ec = acc.open(corosio::tcp::v4())) + return; // report the error + + // Lets bind() succeed while connections from a previous listener are + // still in TIME_WAIT -- the difference between a server that restarts + // and one that fails with address_in_use. It does not let two live + // listeners share a port; that is reuse_port. Must precede bind(). + // set_option reports failure by throwing, not by returning a code. + acc.set_option(corosio::socket_option::reuse_address(true)); + + if (auto ec = acc.bind(corosio::endpoint(8080))) + return; // report the error + if (auto ec = acc.listen()) + return; // report the error +} +// end::reuse_address[] + +} // namespace diff --git a/test/doc/reference/socket_option__reuse_port.record.cpp b/test/doc/reference/socket_option__reuse_port.record.cpp new file mode 100644 index 000000000..10733984e --- /dev/null +++ b/test/doc/reference/socket_option__reuse_port.record.cpp @@ -0,0 +1,51 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/socket_option.hpp's +// documentation for socket_option::reuse_port, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include +#include +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::reuse_port[] +void share_one_port_across_several_acceptors(corosio::tcp_acceptor& acc) +{ + if (auto ec = acc.open(corosio::tcp::v6())) + return; // report the error + + // Every acceptor that sets this -- typically one per thread or process -- + // may bind the same port at the same time, and the kernel spreads + // incoming connections across them. reuse_address is the weaker relative: + // it only permits rebinding a port no longer being listened on. + // + // set_option reports failure by throwing, not by returning a code -- + // including on a platform with no SO_REUSEPORT at all, where it throws + // std::system_error. + acc.set_option(corosio::socket_option::reuse_port(true)); + + if (auto ec = acc.bind( + corosio::endpoint(corosio::ipv6_address::any(), 8080))) + return; // report the error + if (auto ec = acc.listen()) + return; // report the error +} +// end::reuse_port[] + +} // namespace diff --git a/test/doc/reference/socket_option__send_buffer_size.record.cpp b/test/doc/reference/socket_option__send_buffer_size.record.cpp new file mode 100644 index 000000000..e15499c5d --- /dev/null +++ b/test/doc/reference/socket_option__send_buffer_size.record.cpp @@ -0,0 +1,33 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/socket_option.hpp's +// documentation for socket_option::send_buffer_size, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::send_buffer_size[] +void widen_the_send_buffer(corosio::tcp_socket& sock) +{ + // Room for the kernel to hold data the peer has not acknowledged yet; + // worth raising on a high-bandwidth, high-latency path. + sock.set_option(corosio::socket_option::send_buffer_size(65536)); +} +// end::send_buffer_size[] + +} // namespace diff --git a/test/doc/reference/socket_option__v6_only.record.cpp b/test/doc/reference/socket_option__v6_only.record.cpp new file mode 100644 index 000000000..198250ab2 --- /dev/null +++ b/test/doc/reference/socket_option__v6_only.record.cpp @@ -0,0 +1,51 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/socket_option.hpp's +// documentation for socket_option::v6_only, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include +#include +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::v6_only[] +void accept_ipv6_peers_only(corosio::tcp_acceptor& acc) +{ + if (auto ec = acc.open(corosio::tcp::v6())) + return; // report the error + + // Set between open() and bind(): once bound, the option no longer moves. + // Disabled, an IPv6 acceptor also accepts IPv4 peers and reports them as + // v4-mapped addresses. tcp_acceptor::open() leaves the acceptor dual-stack + // (v6_only false) on every backend; tcp_socket and udp_socket are the + // opposite, their open() making an IPv6 socket v6-only. Set the option + // explicitly whenever either behavior matters, rather than relying on a + // default that differs by object. set_option reports failure by throwing, + // not by returning a code. + acc.set_option(corosio::socket_option::v6_only(true)); + + if (auto ec = acc.bind( + corosio::endpoint(corosio::ipv6_address::any(), 8080))) + return; // report the error + if (auto ec = acc.listen()) + return; // report the error +} +// end::v6_only[] + +} // namespace diff --git a/test/doc/reference/stream_file.record.cpp b/test/doc/reference/stream_file.record.cpp new file mode 100644 index 000000000..4c479d89a --- /dev/null +++ b/test/doc/reference/stream_file.record.cpp @@ -0,0 +1,52 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/stream_file.hpp's +// documentation for stream_file, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what +// the reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include +#include + +#include +#include +#include + +namespace corosio = boost::corosio; +namespace capy = boost::capy; + +namespace { + +// tag::stream_file[] +// read_some has an implicit position: it advances automatically after +// each call, unlike random_access_file's explicit offset. +capy::task<> read_a_file_until_eof(corosio::io_context& ioc) +{ + corosio::stream_file f(ioc); + if (auto ec = f.open("data.bin", corosio::file_base::read_only)) + co_return; // report the error + + char buf[4096]; + for (;;) + { + auto [ec, n] = co_await f.read_some( + capy::mutable_buffer(buf, sizeof(buf))); + if (ec == capy::cond::eof) + break; + if (ec) + co_return; + } +} +// end::stream_file[] + +} // namespace diff --git a/test/doc/reference/tcp.record.cpp b/test/doc/reference/tcp.record.cpp new file mode 100644 index 000000000..f47fb898b --- /dev/null +++ b/test/doc/reference/tcp.record.cpp @@ -0,0 +1,50 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/tcp.hpp's +// documentation for tcp, by doc/addons/extensions/reference-snippets.lua. +// The tagged region is what the reference renders; scaffolding stays +// outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include +#include +#include +#include +#include + +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::tcp[] +// set_option throws rather than returning an error code, unlike +// open/bind/listen. Precondition: acc is not already open -- open() is a +// no-op on an already-open acceptor, so an acceptor left over from a v4 +// attempt would silently keep its v4 socket and fail later at bind(). +std::error_code open_an_ipv6_listener(corosio::io_context& ioc) +{ + corosio::tcp_acceptor acc(ioc); + if (auto ec = acc.open(corosio::tcp::v6())) // IPv6 socket + return ec; + acc.set_option(corosio::socket_option::reuse_address(true)); + if (auto ec = acc.bind( + corosio::endpoint(corosio::ipv6_address::any(), 8080))) + return ec; + if (auto ec = acc.listen()) + return ec; + return {}; +} +// end::tcp[] + +} // namespace diff --git a/test/doc/reference/tcp_acceptor.record.cpp b/test/doc/reference/tcp_acceptor.record.cpp new file mode 100644 index 000000000..b428e6302 --- /dev/null +++ b/test/doc/reference/tcp_acceptor.record.cpp @@ -0,0 +1,77 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/tcp_acceptor.hpp's +// documentation for tcp_acceptor, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +namespace corosio = boost::corosio; +namespace capy = boost::capy; + +namespace { + +// tag::convenience_construction[] +// open + SO_REUSEADDR/SO_EXCLUSIVEADDRUSE + bind + listen in one call. +// Throws std::system_error if any of those steps fails. +capy::task<> accept_with_the_convenience_constructor(corosio::io_context& ioc) +{ + corosio::tcp_acceptor acc(ioc, corosio::endpoint(8080)); + + corosio::tcp_socket peer(ioc); + auto [ec] = co_await acc.accept(peer); + if (!ec) + { + // peer is now a connected socket + char storage[1024]; + auto [rec, n] = co_await peer.read_some( + capy::mutable_buffer(storage, sizeof(storage))); + if (rec) + co_return; + } +} +// end::convenience_construction[] + +// tag::fine_grained_setup[] +// set_option throws rather than returning an error code, unlike +// open/bind/listen. Precondition: acc is not already open -- open() is a +// no-op on an already-open acceptor, so an acceptor left over from a v4 +// attempt would silently keep its v4 socket and fail later at bind(). +std::error_code open_ipv6_explicitly(corosio::io_context& ioc) +{ + corosio::tcp_acceptor acc(ioc); + if (auto ec = acc.open(corosio::tcp::v6())) + return ec; + acc.set_option(corosio::socket_option::reuse_address(true)); + acc.set_option(corosio::socket_option::v6_only(true)); + if (auto ec = acc.bind( + corosio::endpoint(corosio::ipv6_address::any(), 8080))) + return ec; + if (auto ec = acc.listen()) + return ec; + return {}; +} +// end::fine_grained_setup[] + +} // namespace diff --git a/test/doc/reference/tcp_acceptor__accept.function.cpp b/test/doc/reference/tcp_acceptor__accept.function.cpp new file mode 100644 index 000000000..6ea79bfbc --- /dev/null +++ b/test/doc/reference/tcp_acceptor__accept.function.cpp @@ -0,0 +1,84 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/tcp_acceptor.hpp's +// documentation for tcp_acceptor::accept, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. +// +// Two overloads, two named regions in one file: `accept(tcp_socket&)` fills +// a socket the caller already owns and can reuse across connections +// (accept_into_a_reused_socket); `accept()` returns a fresh socket with no +// caller-owned socket to reuse (accept_returning_a_new_socket). Both share +// the slug +// `tcp_acceptor__accept.function` -- naming each region keeps the marker +// bound to its own overload regardless of visit order. + +#include "../doc_warnings.hpp" + +#include +#include + +#include +#include + +namespace corosio = boost::corosio; +namespace capy = boost::capy; + +namespace { + +// tag::accept_into_a_reused_socket[] +// Precondition: acc is open, bound, and listening. peer must share acc's +// execution context; constructing it from acc.context() ties the two +// structurally instead of leaving the pairing to be asserted in prose. +capy::task<> accept_into_a_reused_socket(corosio::tcp_acceptor& acc) +{ + // The caller owns peer and can accept into it repeatedly -- its + // lifetime outlives any single connection, unlike the value-returning + // overload's socket, which is fresh on every call. This is the case + // tcp_server::worker_base is built on: one socket per worker, reused + // for each connection it handles in turn. + corosio::tcp_socket peer(acc.context()); + + for (;;) + { + auto [ec] = co_await acc.accept(peer); + if (ec) + co_return; + + char msg[] = "ping"; + auto [wec, n] = co_await peer.write_some( + capy::const_buffer(msg, 4)); + if (wec) + co_return; + + peer.close(); // ready to accept the next connection into peer + } +} +// end::accept_into_a_reused_socket[] + +// tag::accept_returning_a_new_socket[] +// Precondition: acc is open, bound, and listening. +capy::task<> accept_returning_a_new_socket(corosio::tcp_acceptor& acc) +{ + // Each call returns a fresh socket sharing acc's execution context -- + // there is no caller-owned socket to reuse, unlike accept(tcp_socket&). + auto [ec, peer] = co_await acc.accept(); + if (ec) + co_return; + + char msg[] = "ping"; + auto [wec, n] = co_await peer.write_some( + capy::const_buffer(msg, 4)); + if (wec) + co_return; +} +// end::accept_returning_a_new_socket[] + +} // namespace diff --git a/test/doc/reference/tcp_acceptor__get_option.function.cpp b/test/doc/reference/tcp_acceptor__get_option.function.cpp new file mode 100644 index 000000000..ba5305296 --- /dev/null +++ b/test/doc/reference/tcp_acceptor__get_option.function.cpp @@ -0,0 +1,34 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/tcp_acceptor.hpp's +// documentation for tcp_acceptor::get_option, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::get_option[] +// Precondition: acc is open (get_option throws bad_file_descriptor +// otherwise). +bool reuse_address_is_enabled(corosio::tcp_acceptor& acc) +{ + auto opt = acc.get_option(); + return opt.value(); +} +// end::get_option[] + +} // namespace diff --git a/test/doc/reference/tcp_acceptor__open.function.cpp b/test/doc/reference/tcp_acceptor__open.function.cpp new file mode 100644 index 000000000..ce08b33c0 --- /dev/null +++ b/test/doc/reference/tcp_acceptor__open.function.cpp @@ -0,0 +1,48 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/tcp_acceptor.hpp's +// documentation for tcp_acceptor::open, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include +#include +#include +#include + +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::open[] +// set_option throws rather than returning an error code, unlike +// open/bind/listen. Precondition: acc is not already open -- open() is a +// no-op on an already-open acceptor, so an acceptor left over from a v4 +// attempt would silently keep its v4 socket and fail later at bind(). +std::error_code open_bind_and_listen(corosio::tcp_acceptor& acc) +{ + if (auto ec = acc.open(corosio::tcp::v6())) + return ec; + acc.set_option(corosio::socket_option::reuse_address(true)); + if (auto ec = acc.bind( + corosio::endpoint(corosio::ipv6_address::any(), 8080))) + return ec; + if (auto ec = acc.listen()) + return ec; + return {}; +} +// end::open[] + +} // namespace diff --git a/test/doc/reference/tcp_acceptor__set_option.function.cpp b/test/doc/reference/tcp_acceptor__set_option.function.cpp new file mode 100644 index 000000000..3b03df017 --- /dev/null +++ b/test/doc/reference/tcp_acceptor__set_option.function.cpp @@ -0,0 +1,48 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/tcp_acceptor.hpp's +// documentation for tcp_acceptor::set_option, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include +#include +#include +#include + +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::set_option[] +// set_option throws rather than returning an error code, unlike +// open/bind/listen. Precondition: acc is not already open -- open() is a +// no-op on an already-open acceptor, so an acceptor left over from a v4 +// attempt would silently keep its v4 socket and fail later at bind(). +std::error_code open_with_reuse_port(corosio::tcp_acceptor& acc) +{ + if (auto ec = acc.open(corosio::tcp::v6())) + return ec; + acc.set_option(corosio::socket_option::reuse_port(true)); + if (auto ec = acc.bind( + corosio::endpoint(corosio::ipv6_address::any(), 8080))) + return ec; + if (auto ec = acc.listen()) + return ec; + return {}; +} +// end::set_option[] + +} // namespace diff --git a/test/doc/reference/tcp_server.record.cpp b/test/doc/reference/tcp_server.record.cpp new file mode 100644 index 000000000..cb4f0c614 --- /dev/null +++ b/test/doc/reference/tcp_server.record.cpp @@ -0,0 +1,154 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/tcp_server.hpp's +// documentation for tcp_server, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace corosio = boost::corosio; +namespace capy = boost::capy; + +namespace { + +// tag::running_the_server[] +// Stopped -> Running: bind before start, start before run. The worker pool +// must be built against the same io_context the server itself runs on. +void run_the_server( + corosio::io_context& ioc, + std::vector> workers) +{ + corosio::tcp_server srv(ioc, ioc.get_executor()); + srv.set_workers(std::move(workers)); + if (auto ec = srv.bind( + corosio::endpoint{corosio::ipv4_address::any(), 8080})) + return; // report the error + srv.start(); + ioc.run(); // Blocks until all work completes +} +// end::running_the_server[] + +// tag::graceful_shutdown[] +// Precondition: srv is Running, and ioc is the io_context it was started on. +// To shut down gracefully, call stop then drain the io_context. +void shut_down_gracefully(corosio::io_context& ioc, corosio::tcp_server& srv) +{ + // stop() is the only call here that may come from another context -- + // a signal handler or a timer callback typically makes it while the + // two calls below are running on this thread. + srv.stop(); + + // Back on the thread that owns ioc: run() drains pending completions -- + // this is what actually finishes the accept loops stop() only requested + // the end of. + ioc.run(); + + // Once ioc.run() returns: + srv.join(); // Wait for accept loops to finish +} +// end::graceful_shutdown[] + +// tag::restart_after_stop[] +// Precondition: srv is bound and has workers. The server can be restarted +// after a complete shutdown cycle; you must drain the io_context, call +// join, and restart the io_context itself before restarting the server. +void restart_after_stop(corosio::io_context& ioc, corosio::tcp_server& srv) +{ + using namespace std::chrono_literals; + + srv.start(); + ioc.run_for( 10s ); // Run for a while + srv.stop(); // Signal shutdown + + // REQUIRED: stop() only requests the accept loops end -- it does not + // drive them to completion itself. Only running the executor does: + // ioc.run() is what actually finishes the loops and brings + // active_accepts_ back to zero. + ioc.run(); // REQUIRED: drain pending completions + + // REQUIRED: start() throws std::logic_error if a previous session's + // accept loops have not yet reached zero; join blocks until they have. + srv.join(); // REQUIRED: wait for accept loops + + // REQUIRED: the reactor scheduler stops itself once its outstanding + // work reaches zero (which draining above just caused), so ioc.run() + // below would return immediately without restart() -- the posted + // accept loops would never actually run, and join() would then block + // forever waiting for a completion that never happens. + ioc.restart(); // REQUIRED: io_context must be restarted too + + // Now safe to restart + srv.start(); + ioc.run(); +} +// end::restart_after_stop[] + +// tag::custom_worker[] +// A worker owns the socket it hands to each connection and is returned to +// the pool when its coroutine completes; deriving from worker_base is what +// makes an object eligible for set_workers. The executor is fetched from +// ctx_ at launch time rather than stored as a capy::any_executor: launcher +// dispatches by posting a coroutine_handle directly, which the type-erased +// any_executor has no overload for. +class my_worker : public corosio::tcp_server::worker_base +{ + corosio::io_context& ctx_; + corosio::tcp_socket sock_; +public: + my_worker(corosio::io_context& ctx) + : ctx_(ctx) + , sock_(ctx) + { + } + + corosio::tcp_socket& socket() override { return sock_; } + + void run(corosio::tcp_server::launcher launch) override + { + launch(ctx_.get_executor(), [](corosio::tcp_socket* sock) -> capy::task<> + { + // handle connection using sock + co_return; + }(&sock_)); + } +}; + +auto make_workers(corosio::io_context& ctx, int n) +{ + std::vector> v; + v.reserve(n); + for(int i = 0; i < n; ++i) + v.push_back(std::make_unique(ctx)); + return v; +} + +void build_a_worker_pool() +{ + corosio::io_context ioc; + corosio::tcp_server srv(ioc, ioc.get_executor()); + srv.set_workers(make_workers(ioc, 100)); +} +// end::custom_worker[] + +} // namespace diff --git a/test/doc/reference/tcp_server__join.function.cpp b/test/doc/reference/tcp_server__join.function.cpp new file mode 100644 index 000000000..1d708f114 --- /dev/null +++ b/test/doc/reference/tcp_server__join.function.cpp @@ -0,0 +1,68 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/tcp_server.hpp's +// documentation for tcp_server::join, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include +#include + +#include + +namespace corosio = boost::corosio; +namespace capy = boost::capy; + +namespace { + +// tag::correct_usage[] +// Precondition: srv is bound and has workers. +void run_server_to_completion( + corosio::io_context& ioc, corosio::tcp_server& srv) +{ + // main thread + srv.start(); + ioc.run(); // Blocks until work completes + srv.join(); // Safe: called after ioc.run() returns +} +// end::correct_usage[] + +// tag::deadlock_scenarios[] +// WRONG: calling join() from inside a worker coroutine +class self_joining_worker : public corosio::tcp_server::worker_base +{ + corosio::io_context& ctx_; + corosio::tcp_socket sock_; + corosio::tcp_server& srv_; +public: + self_joining_worker(corosio::io_context& ctx, corosio::tcp_server& srv) + : ctx_(ctx) + , sock_(ctx) + , srv_(srv) + { + } + + corosio::tcp_socket& socket() override { return sock_; } + + void run(corosio::tcp_server::launcher launch) override + { + launch(ctx_.get_executor(), [this]() -> capy::task<> + { + srv_.join(); // DEADLOCK: blocks the executor + co_return; + }()); + } +}; +// end::deadlock_scenarios[] + +} // namespace diff --git a/test/doc/reference/tcp_server__set_workers.function.cpp b/test/doc/reference/tcp_server__set_workers.function.cpp new file mode 100644 index 000000000..5ec88b5dc --- /dev/null +++ b/test/doc/reference/tcp_server__set_workers.function.cpp @@ -0,0 +1,79 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/tcp_server.hpp's +// documentation for tcp_server::set_workers, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. +// +// NOTE (task-8-report.md): the plan predicted this file would cover `bind` +// (the range-constrained template it flagged sits at roughly line 654). Line +// 654 is inside set_workers's own docstring, not bind's -- bind takes a +// single endpoint and has no @code example at all. The tool that generated +// symbol-map.txt mis-parsed the declaration and printed "decltype" as the +// symbol name for the same reason a human skimming the requires-clause +// might. This file is named for the symbol the marker actually sits under. + +#include "../doc_warnings.hpp" + +#include +#include +#include + +#include + +#include +#include +#include + +namespace corosio = boost::corosio; +namespace capy = boost::capy; + +namespace { + +// Minimal concrete worker, only to give set_workers's own example something +// to build a vector of. tcp_server.record.cpp's `custom_worker` region is +// the reference's full illustration of implementing a worker. +class my_worker : public corosio::tcp_server::worker_base +{ + corosio::io_context& ctx_; + corosio::tcp_socket sock_; +public: + my_worker(corosio::io_context& ctx) + : ctx_(ctx) + , sock_(ctx) + { + } + + corosio::tcp_socket& socket() override { return sock_; } + + void run(corosio::tcp_server::launcher launch) override + { + launch(ctx_.get_executor(), [](corosio::tcp_socket* sock) -> capy::task<> + { + co_return; + }(&sock_)); + } +}; + +// tag::set_workers[] +// Precondition: none the type system enforces on srv's state, but calling +// this while srv is running discards any worker mid-connection -- the +// idle/active lists are cleared before the new pool is populated. +void configure_the_worker_pool( + corosio::io_context& ctx, corosio::tcp_server& srv) +{ + std::vector> workers; + for(int i = 0; i < 100; ++i) + workers.push_back(std::make_unique(ctx)); + srv.set_workers(std::move(workers)); +} +// end::set_workers[] + +} // namespace diff --git a/test/doc/reference/tcp_server__start.function.cpp b/test/doc/reference/tcp_server__start.function.cpp new file mode 100644 index 000000000..2ebf4848b --- /dev/null +++ b/test/doc/reference/tcp_server__start.function.cpp @@ -0,0 +1,51 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/tcp_server.hpp's +// documentation for tcp_server::start, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include + +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::start[] +// Precondition: srv is bound and has workers, and is Stopped -- either +// fresh, or after a complete prior stop()/run()/join() cycle. +void restart_after_full_drain(corosio::io_context& ioc, corosio::tcp_server& srv) +{ + using namespace std::chrono_literals; + + srv.start(); + ioc.run_for( 1s ); + srv.stop(); // 1. Signal shutdown + ioc.run(); // 2. Drain remaining completions + srv.join(); // 3. Wait for accept loops + + // 4. Restart the io_context itself: draining above ran its outstanding + // work to zero, which stops it, so io_context::run() below would + // otherwise return immediately without ever running the posted + // accept loops. + ioc.restart(); + + // Now safe to restart + srv.start(); + ioc.run(); +} +// end::start[] + +} // namespace diff --git a/test/doc/reference/tcp_server__tcp_server.function.cpp b/test/doc/reference/tcp_server__tcp_server.function.cpp new file mode 100644 index 000000000..a562d9c41 --- /dev/null +++ b/test/doc/reference/tcp_server__tcp_server.function.cpp @@ -0,0 +1,44 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/tcp_server.hpp's +// documentation for tcp_server::tcp_server, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include +#include +#include + +#include +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::tcp_server[] +void construct_and_start( + corosio::io_context& ctx, + std::vector> workers) +{ + corosio::tcp_server srv(ctx, ctx.get_executor()); + srv.set_workers(std::move(workers)); + if (auto ec = srv.bind( + corosio::endpoint{corosio::ipv4_address::any(), 8080})) + return; // report the error + srv.start(); +} +// end::tcp_server[] + +} // namespace diff --git a/test/doc/reference/tcp_socket.record.cpp b/test/doc/reference/tcp_socket.record.cpp new file mode 100644 index 000000000..310b51a03 --- /dev/null +++ b/test/doc/reference/tcp_socket.record.cpp @@ -0,0 +1,49 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/tcp_socket.hpp's +// documentation for tcp_socket, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include +#include +#include + +#include +#include + +namespace corosio = boost::corosio; +namespace capy = boost::capy; + +namespace { + +// tag::connect_and_read[] +capy::task<> connect_and_read(corosio::io_context& ioc) +{ + corosio::tcp_socket s(ioc); + + // Using structured bindings + auto [ec] = co_await s.connect( + corosio::endpoint(corosio::ipv4_address::loopback(), 8080)); + if (ec) + co_return; + + char buf[1024]; + auto [read_ec, n] = co_await s.read_some( + capy::mutable_buffer(buf, sizeof(buf))); + if (read_ec) + co_return; +} +// end::connect_and_read[] + +} // namespace diff --git a/test/doc/reference/tcp_socket__connect.function.cpp b/test/doc/reference/tcp_socket__connect.function.cpp new file mode 100644 index 000000000..d317a0813 --- /dev/null +++ b/test/doc/reference/tcp_socket__connect.function.cpp @@ -0,0 +1,45 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/tcp_socket.hpp's +// documentation for tcp_socket::connect, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. + +#include "../doc_warnings.hpp" + +#include +#include +#include + +#include + +namespace corosio = boost::corosio; +namespace capy = boost::capy; + +namespace { + +// tag::connect[] +capy::task<> connect_to_a_server(corosio::io_context& ioc, corosio::endpoint ep) +{ + // s is freshly constructed and so is not yet open: connect() only + // opens the socket automatically when it is not already open, using + // ep's address family; an already-open socket keeps its existing + // descriptor and family instead. + corosio::tcp_socket s(ioc); + + auto [ec] = co_await s.connect(ep); + if (ec) + co_return; + + // s is now connected. +} +// end::connect[] + +} // namespace diff --git a/test/doc/reference/tcp_socket__get_option.function.cpp b/test/doc/reference/tcp_socket__get_option.function.cpp new file mode 100644 index 000000000..443013fe1 --- /dev/null +++ b/test/doc/reference/tcp_socket__get_option.function.cpp @@ -0,0 +1,52 @@ +// +// Copyright (c) 2026 Michael Vandeberg +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/corosio +// + +// Reference example injected into include/boost/corosio/tcp_socket.hpp's +// documentation for tcp_socket::get_option, by +// doc/addons/extensions/reference-snippets.lua. The tagged region is what the +// reference renders; scaffolding stays outside the tags. +// +// Round 1 review: the first fix here set no_delay before reading it back, to +// make the shipped `// true: Nagle's algorithm is off` comment true (see the +// git history for that finding -- it stands, TCP_NODELAY is never set +// automatically). But that made this page a near-duplicate of +// socket_option__no_delay.record.cpp's own round-trip example. This version +// instead teaches what is specific to get_option as a member: Option is an +// explicit template argument, never deduced, and the call throws rather than +// returning an error code. + +#include "../doc_warnings.hpp" + +#include +#include + +namespace corosio = boost::corosio; + +namespace { + +// tag::get_option[] +// Precondition: sock is open (get_option throws bad_file_descriptor +// otherwise, and throws again if the underlying getsockopt call fails). +// Option is always an explicit template argument -- it is never deduced +// from sock or from any function argument. +template +Option read_option(corosio::tcp_socket& sock) +{ + return sock.get_option