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