Skip to content

To master#265

Merged
sgerbino merged 227 commits into
masterfrom
develop
Jun 2, 2026
Merged

To master#265
sgerbino merged 227 commits into
masterfrom
develop

Conversation

@sgerbino
Copy link
Copy Markdown
Collaborator

@sgerbino sgerbino commented Jun 2, 2026

No description provided.

sgerbino and others added 30 commits February 2, 2026 15:43
wc_KeyPemToDer decrypts PEM keys and wc_DecryptPKCS8Key handles
encrypted DER, avoiding wolfSSL_CTX_set_default_passwd_cb which
requires OPENSSL_EXTRA.
Automatically detect and enable Link Time Optimization for both
Corosio and Asio benchmarks. This ensures fair comparisons and
optimal performance measurement.
Remove the separate lambda-based benchmark and keep only the coroutine
version for fair comparison with corosio. Both libraries now benchmark
coroutine dispatch rather than comparing coroutines against callbacks.
Replace per-iteration timeout calculation with Linux timerfd for
kernel-managed timer expiry. Add task operation sentinel pattern
to interleave reactor runs with handler execution, preventing
timer starvation.
- Remove unused any_completed variable
- Skip redundant error handling when EPOLLIN/EPOLLOUT already processed ops
- Simplify thread notification: notify_one for single completion,
  notify_all for multiple completions
- Add task sentinel pattern for timer starvation prevention
- Simplify do_one() by removing deadline tracking
- Optimize thread notification logic
- Remove unused includes
Use initiator coroutines for read/write operations to ensure the
caller's coroutine is fully suspended before I/O starts. This prevents
a race condition in multi-threaded scenarios where immediate completion
could try to resume a coroutine that isn't fully suspended yet.

- Add read_initiator and write_initiator coroutine types with cached
  frame allocation to avoid per-operation heap allocations
- Move I/O logic to do_read_io() and do_write_io() methods
- Modify read_some/write_some to return initiator handles for symmetric
  transfer
- Add destructor to clean up initiator frames and handles
Consistent with other syscall error handling in this file.
Combine four separate benchmark executables into one for each library:
- asio_bench: unified Asio benchmarks with --category and --bench filters
- corosio_bench: unified Corosio benchmarks with --backend, --category, and --bench filters

Extract make_socket_pair into shared socket_utils.hpp to reduce duplication.
- Add warmup phase to all benchmarks to reduce variance
- Remove category headers from output
- Increase iterations for more stable results:
  - io_context: 5M handlers
  - socket_latency: 1M iterations
  - socket_throughput: 4GB transfer
  - http_server: 1M requests
The NT wait completion packet API requires one-shot re-association
after every wakeup, causing ~60% CPU overhead in timer-free workloads.
Switch to thread-based implementation which has lower per-iteration cost.

Also tidy up NT function pointer loading to cast once at init time.
Reduce mutex contention by processing events into a local queue
without holding the mutex. The mutex is only acquired briefly
when splicing completions into the completed_ops_ queue.

Changes:
- Process events into a local op_queue without holding the mutex
- Only acquire mutex for completed_ops_ splice operation
- Add check_timers flag to only process timers when timerfd fires
- Cache last timerfd expiry to skip redundant timerfd_settime calls
Only wake idle threads, and only as many as we have work available.
This prevents waking all threads when only a few completions arrive.
When posting work from within the scheduler's run loop, use a
thread-local queue instead of acquiring the global mutex. This
matches Asio's thread_info::private_op_queue optimization.

- Extend scheduler_context with private_queue and work counter
- Fast path in post() detects same-thread via context_stack
- Drain points: before blocking, after reactor splice, on exit
- Reduces futex calls from ~450K to 1 in multi-threaded benchmarks
- Consume timerfd on expiry to prevent epoll busy-spinning
  (level-triggered fd must be read to clear readable state)

- Remove last_timerfd_expiry_ caching optimization to match Asio
  (eliminates data race between timer callback and reactor thread)
mvandeberg and others added 25 commits April 17, 2026 11:47
Free functions in boost/corosio/connect.hpp that try each endpoint in a
range (or iterator pair) until one connects, with optional connect-condition
predicate. Mirrors Boost.Asio's async_connect semantics adapted to
corosio's coroutine model.
…arison

Add _lockless variants to asio and asio_callback across socket_throughput,
socket_latency, local_socket_throughput, local_socket_latency, fan_out,
accept_churn, http_server, and timer, mirroring corosio's single-threaded
configurations by constructing the io_context with
BOOST_ASIO_CONCURRENCY_HINT_UNSAFE. Follows the existing pattern in
io_context_bench.cpp.

Move throughput byte accounting out of the read loop into a local int64_t
accumulator, calling state.add_bytes once after ioc.run() returns. The
previous per-read state.add_bytes was an atomic fetch_add on every
completion, which added ~20ns × N_reads to the measured elapsed time and
structurally disadvantaged the faster library at small chunk sizes.
Multithread benches still use atomic aggregation (required for correctness
across N runner threads).
Add --warmup <secs> flag (default 0, disabled) that runs every benchmark
once with a throwaway state before the real measurement. The self-warmup
exercises the exact code path being measured, eliminating the sync-vs-async
warmup asymmetry in the previous scheme where corosio's set_warmup lambdas
exercised async I/O paths while asio's used synchronous asio::write/read.

Removes benchmark_suite::set_warmup and all 18 .set_warmup([]{...}) call
sites across corosio, asio coroutine, and asio callback. For benches with
needs_conntrack_drain, drain runs before both warmup and real measurement.
Replace unique_ptr<tcp_socket> with value-typed sockets across all asio
accept_churn benchmarks (coroutine and callback variants). Vectors are
pre-reserved so no reallocation invalidates references held by pending
async operations. This matches the corosio benchmark's allocation
pattern for an apples-to-apples comparison.
Adds 4.guide/4q.udp.adoc covering the corosio UDP API: udp protocol tag,
open/bind, connectionless and connected modes, message flags, socket
options, multicast, cancellation, and a TCP-vs-UDP comparison. Wires the
new page into nav.adoc after Unix Domain Sockets.
The windows-2025 runner label transiently resolved to a
windows-2025-vs2026 image (Visual Studio 18 preview, MSVC 14.50)
instead of the documented VS 2022 image, breaking the configure step
because the hardcoded "Visual Studio 17 2022" generator can't match a
VS 18 install.

Pin the latest MSVC entry to windows-2022, which has VS 2022 stable,
until VS 18 reaches RTM and CMake ships the corresponding generator.
Also bump the matrix version label from 14.42 to 14.44 so the job name
matches the toolset actually shipped on windows-2022 (14.44.35207).
When the speculative read/write only has one buffer, dispatch to recv()/
send() instead of readv()/writev()/sendmsg() so the kernel can skip the
iov_iter setup. The single-buffer path is the common case in HTTP and
streaming workloads.

The single-buffer write goes through a new write_policy::write_one
backend hook so each reactor backend picks the right SIGPIPE strategy:

  - epoll:  send(MSG_NOSIGNAL)
  - kqueue: write() -- macOS lacks MSG_NOSIGNAL; SIGPIPE is suppressed
            by the mandatory SO_NOSIGPIPE set in accept_policy and
            set_fd_options
  - select: send(MSG_NOSIGNAL) where defined, else write() with
            SO_NOSIGPIPE as the SIGPIPE fallback

The single-buffer read uses recv() inline -- flag=0 is portable across
all reactor platforms, so no read_policy is needed.
Two related improvements to io_context construction:

1. Static heuristic for inline budget defaults: when concurrency_hint
   > 1 and the user has not customized budget options, override
   (init, max, unassisted) to (0, 0, 0). Multi-thread workloads
   benefit from cross-thread work-stealing, which "post everything"
   enables; single-thread keeps the (2, 16, 4) chaining defaults.

2. Tie concurrency_hint == 1 to single_threaded mode (asio precedent):
   constructing an io_context with concurrency_hint == 1 now forces
   opts.single_threaded = true, eliminating scheduler locking on the
   hot path. Matches asio's one_thread_ behavior. To opt out, pass
   concurrency_hint > 1.

Supporting changes:
  - reactor_scheduler: short-circuit try_consume_inline_budget and
    reset_inline_budget when inline_budget_max_ == 0 so post-everything
    mode skips TLS-list walks; relax configure_reactor validation to
    allow budget_max == 0; fix max(1, x) * 2 in ramp-up to escape zero.
  - io_context: apply_options_pre_/post_ now take concurrency_hint;
    new private configure_single_threaded_() helper used by the
    bare-hint and template no-opts constructors.
  - docs: update 4c2.configuration.adoc and io_context_options field
    docs to describe the queue-empty gating and new defaults.
Sets each worker's thread name via capy::set_current_thread_name so
they show up in debuggers and system tools as tpool-svc-1, tpool-svc-2,
etc. Format fits Linux's 15-char pthread name limit for indices up to
9999.
Adds boost::corosio::host_name(), a synchronous free function returning
the local machine's hostname as a UTF-8 std::string. Common uses:
logging, default Host: headers, cluster registration, identifying a
node.

A single overload that throws std::runtime_error on failure. No
std::error_code variant: failures of the underlying syscalls are
pathological on a configured machine, and the throwing form is
sufficient.

POSIX path uses gethostname(2) with a 256-byte stack buffer, which
comfortably exceeds the _POSIX_HOST_NAME_MAX floor of 255 and every
mainstream OS's actual cap. errno is captured immediately on failure.
POSIX does not guarantee NUL termination on truncation, so the
implementation checks for a NUL anywhere in the buffer and throws
otherwise.

Windows path uses GetComputerNameExW(ComputerNameDnsHostname, ...)
followed by WideCharToMultiByte(CP_UTF8, ...) to produce UTF-8.
Chosen over winsock gethostname() so the function works without
WSAStartup(); in corosio winsock is initialized lazily inside
io_context, so a winsock-based implementation would tie host_name()
to io_context lifetime, contradicting the "no io_context needed"
design goal.

Adds five unit tests covering: non-empty result, stability across
calls, reasonable length (<= 255 octets), no-io_context-needed
(load-bearing regression guard for the Windows implementation
choice), and charset sanity (catches UTF-16 -> UTF-8 regressions).
cppalliance/capy#262 removes the `const_buffer_pair` /
`mutable_buffer_pair` aliases and demotes `buffer_array<N, IsConst>`
from `capy::` to `capy::detail::`. corosio migrates:

- `test/unit/buffer_param.cpp`: replace `capy::const_buffer_pair` /
  `capy::mutable_buffer_pair` with `std::array<capy::const_buffer, 2>`
  / `std::array<capy::mutable_buffer, 2>`. Drop the
  `buffers/buffer_pair.hpp` include.

- tls_stream.hpp / wolfssl_stream.{hpp,cpp} / openssl_stream.{hpp,cpp}:
  replace `capy::mutable_buffer_array<N>` / `capy::const_buffer_array<N>`
  with `capy::detail::mutable_buffer_array<N>` /
  `capy::detail::const_buffer_array<N>` and update include paths to
  `boost/capy/detail/buffer_array.hpp`. corosio's TLS stream interface
  acknowledges that this scatter/gather fixed-capacity helper is now
  capy-internal; using `detail::` from corosio is the explicit choice
  made when buffer_array became internal machinery.

Coordinated with capy PR cppalliance/capy#262.
Both headers are standalone public APIs not reached transitively from
anything else in the umbrella, so users who include only
<boost/corosio.hpp> could not call corosio::connect or corosio::host_name
without an extra include.
Exposes an asio-style wait(wait_type) awaitable on tcp_socket,
udp_socket, local_stream_socket, local_datagram_socket, tcp_acceptor,
and local_stream_acceptor. The operation suspends until the descriptor
is ready in the chosen direction (read / write / error) without
transferring any bytes; useful for wrapping nonblocking C libraries
(libssh, libpq async, etc.) that own their own recv/send and only need
readiness notification.

POSIX backends share a new reactor_wait_op<Base> template that parks
in three new wait_*_op slots on reactor_descriptor_state. Dispatch in
invoke_deferred_io completes the matching slot on each event bit
without performing any I/O syscall.

wait_type::write completes immediately on a connected socket on every
backend, matching asio's IOCP contract. Corosio's reactor backends
use edge-triggered EPOLLOUT/EVFILT_WRITE; parking would never fire on
an already-writable socket, and backpressure is surfaced through
write_some()'s return value as usual.

On IOCP, stream-socket wait_read uses a zero-byte WSARecv. All other
waits (datagram wait_read, acceptor wait_read, every wait_error)
route through a new win_wait_reactor: a dedicated WSAPoll thread woken
via a loopback Winsock socket pair, bridging readiness into the IOCP
queue via PostQueuedCompletionStatus. The reactor is lazily
constructed via std::call_once and stopped early in
win_scheduler::shutdown() so parked ops do not block work-counter
drain. socket.cancel() / close_socket() / shutdown all route through
the same cancel_wait_if_constructed path so reactor-parked ops are
cleaned up without forcing reactor construction on hot paths that
never used it.

Doxygen on every new public symbol; new Antora guide page
(4r.wait.adoc) covers the readiness pattern, acceptor semantics,
cancellation, and the wait_type::write immediate-ready contract.

Tests parameterised across backends cover the no-consume promise on
TCP, immediate write completion, local_stream wait_read (POSIX), UDP
wait_read (exercises the aux reactor on IOCP), cancellation through
the standard reactor (wait_read on TCP), cancellation while parked in
the aux reactor (UDP wait_read), and acceptor wait_read.
wait_type::error is plumbed but unexercised by tests; kernel error
semantics are non-portable and the contract is documented as
best-effort.
Extend the shadow pattern (concrete-class method names hidden by
derived native_*<Backend> templates that return strongly-typed
awaitables instead of dispatching virtually) so every async
operation on every public I/O type has a native_* counterpart.

Before this branch, only a subset of the async surface was
shadowed. native_tcp_socket and native_tcp_acceptor shadowed
their read/write/accept/connect ops, but native_udp_socket
covered only send_to/recv_from, and the local-socket and file
types had no shadow at all. Code holding a native_X<Backend>
reference therefore went through the virtual io_object dispatch
for any non-shadowed call, defeating the point of using the
typed wrapper.

This commit closes the gap:

  - native_udp_socket gains connect, send, recv shadows.
  - New native_local_stream_socket shadows read_some, write_some,
    connect.
  - New native_local_stream_acceptor shadows accept(peer&) and
    the move-accept overload (returning a
    native_local_stream_socket<Backend>).
  - New native_local_datagram_socket shadows send_to, recv_from,
    connect, send, recv.
  - New native_stream_file shadows read_some, write_some.
  - New native_random_access_file shadows read_some_at,
    write_some_at.
  - Every shadow type also gets wait() returning a typed
    native_wait_awaitable.

Tests under test/unit/native/ exercise each new shadow with a
static_assert that pins the shadowing contract (the native op
must return a type distinct from the concrete base op), plus
runtime checks of the awaitable path and a polymorphic-slice
test that verifies the base class still works via virtual
dispatch when the object is used through its non-native
interface.

Supporting bits:

  - backend.hpp gains the file-type tag typedefs (stream_file_type,
    random_access_file_type, and *_service_type) for every
    backend so create_handle<service_type>(ctx) compiles in the
    new native_*_file wrappers.
  - stream_file and random_access_file grow protected constructors
    so the native virtual-base initialization works (io_stream
    virtually inherits io_object; only the most-derived class
    initializes it).
  - local_stream_acceptor::bind now honors bind_option::unlink_existing
    on Windows via DeleteFileA — the option was previously a no-op
    in the non-POSIX branch, which broke testUnlinkExisting on the
    iocp variant.
  - win_tcp_service::connect_ex / accept_ex getters move into the
    class body so TUs that include only the service header (the
    new native_local_stream_socket test among them) get the
    inline definitions.
  - win_local_stream_service.hpp pulls in the acceptor header
    directly so its inline shutdown() sees the full
    win_local_stream_acceptor_internal type regardless of
    consumer include order.
  - A portable test/unit/local_temp.hpp helper replaces the old
    POSIX-only mkdtemp/unlink pattern in the local-socket tests,
    using std::filesystem + a random_device-seeded RNG so
    parallel ctest processes don't collide on /tmp paths.

Known platform gaps documented in-file:

  - local_datagram_socket tests stay POSIX-only at the
    top-of-file. Windows has never shipped AF_UNIX SOCK_DGRAM;
    the very first WSASocket(AF_UNIX, SOCK_DGRAM, ...) returns
    WSAESOCKTNOSUPPORT.
  - local_stream_socket's three socketpair-based tests
    (testReadWrite, testSocketPair, testAvailable) and the
    raw-fd testRelease remain per-test POSIX-gated;
    make_local_stream_pair is gated POSIX-only in the public
    header because socketpair() doesn't exist on Windows. TCP
    socket tests already exercise the equivalent read/write
    paths on Windows IOCP.
…unt tests

Replace the 10ms delay timer that sequenced waiter registration before
the reset with explicit poll() drains. The previous structure relied on
the IOCP scheduler dispatching the two t.wait() suspensions before the
delay timer expired; on Windows release builds the order isn't
guaranteed and the test would intermittently observe zero canceled
waiters.
- Add Windows make_local_stream_pair() via temp-listener emulation of
  socketpair(), enabling socket-pair tests on IOCP
- Implement assign_socket() in win_local_stream_service so raw SOCKET
  handles can be registered with the IOCP port
- Replace ConnectEx/AcceptEx with blocking connect()/accept() on
  worker threads for AF_UNIX — the IOCP extension functions are not
  reliable for AF_UNIX on all Windows versions
- Add portable temp_socket_dir helper using std::filesystem for
  temp paths across platforms
- Guard local datagram code (SOCK_DGRAM) as POSIX-only at compile time
  — Windows does not support AF_UNIX SOCK_DGRAM
- Remove dead IOCP datagram implementation files
  (win_local_dgram_service.hpp, win_local_dgram_socket.hpp)
- Document Windows limitation on local_datagram_socket and
  local_datagram headers
- socket_option: IP_MULTICAST_LOOP and IP_MULTICAST_TTL are u_char options
  on BSD-derived kernels; the previous int storage caused setsockopt to
  return EINVAL on macOS. Add byte_boolean_option / byte_integer_option
  (public ABI) and byte_boolean<> / byte_integer<> (native templates)
  with single-byte storage; rebase multicast_loop_v4 and multicast_hops_v4
  onto them. IPv6 variants are unaffected.

- native_local_datagram_socket: include of the deleted IOCP datagram
  service was reachable in any IOCP build. Wrap the whole header in
  BOOST_COROSIO_POSIX to mirror local_datagram_socket.hpp.

- posix_resolver_service and win_resolver_service: replace
  make_service<thread_pool>() with use_service<thread_pool>(). The
  original call threw 'invalid argument' whenever
  io_context_options.thread_pool_size != 1 because pre_create_services
  had already constructed the pool with non-default args.

- iocp wait_reactor and tcp_acceptor_service: resolve a pre-cancelled
  stop_token race in wait() and accept() paths that caused tests to
  hang on Windows IOCP under specific completion orderings.
Adds targeted unit tests across the public API and native shadow layers
to close the largest line-coverage gaps in the develop baseline.

Major areas:

- tls_context: cover malformed PEM, mismatched cert/key, cipher list,
  protocol version round-trip, verify mode/depth, hostname, SNI/ALPN,
  CRL, OCSP staple, password callback (24% to 100%).
- io_context and scheduler detail: backend tag construction, run_for,
  run_until, restart, post-throws, deterministic multithreaded
  notify_one + wait_for coverage.
- socket_option: set/get round-trip for every public option plus the
  native templated variants; wrong-protocol error paths.
- local sockets: connect/accept error paths, abstract namespace,
  path-length boundary, mid-flight cancel, datagram send/recv. Adopt
  the temp_socket_dir helper introduced upstream.
- reactor internals: concurrent read/write on same descriptor,
  mid-flight cancel, close-during-op, wait_type::error paths, stop-token
  cancellation. Add reactor_paths.cpp aggregating these scenarios.
- tcp_server, posix_resolver_service, host_name, file services:
  lifecycle, accept loop, flag variants, error mapping.
- Add testConstructionWithThreadPoolSize regression for the resolver
  use_service fix.

Cross-platform reconciliation observed in CI:
- BSD-family kernels reject multicast set_option values (zero buffer
  sizes, leave-without-route, IPV6 ifindex 0) that Linux accepts;
  wrap in try/catch with documented platform variation.
- macOS returns EMSGSIZE for zero-length UDP datagrams; broaden the
  expectation to any error so MinGW and others pass too.
- Windows IOCP: gate testIoContextOptionsMaxEventsZero/BudgetInitClamp
  and the single-threaded resolver tests as POSIX-only; relax buffer
  size assertions to permit Windows's accept-zero-as-zero contract.
- POSIX-guard local socket tests that exercise abstract namespace and
  related Linux-only behavior.
- testMultithreadedNotifyAndWaitFor restructured to depend only on a
  work guard and counter drain, not on wall-clock timing, so it
  remains deterministic under thread-sanitizer.
- Signal-set shutdown test switched from POSIX-only SIGUSR1/2 to the
  portable SIGINT/SIGTERM pair.
The public make_local_stream_pair / make_local_datagram_pair were
primarily used to construct test fixtures; production callers exist
in theory but are vanishingly rare compared to test and benchmark
usage. PR #252's Windows implementation was specifically motivated
by tests on IOCP.

Move both helpers into include/boost/corosio/test/local_socket_pair.hpp
under boost::corosio::test, alongside the existing templated stream
variant that the perf benchmarks already use. The test/ helper drives
bind+accept+connect via the public acceptor API, which works on every
backend after PR #252 enabled AF_UNIX SOCK_STREAM on Windows IOCP --
so the bespoke socketpair-emulation Windows code in
src/corosio/src/local_socket_pair.cpp is no longer needed.

- Delete include/boost/corosio/local_socket_pair.hpp
- Delete src/corosio/src/local_socket_pair.cpp
- Remove the umbrella include in boost/corosio.hpp
- Add make_local_datagram_pair to test/local_socket_pair.hpp (POSIX-only)
- Migrate the three test files that called the public helpers
  (local_stream_socket.cpp, local_datagram_socket.cpp, reactor_paths.cpp)
  via using-declarations so call sites stay identical
Mirror asio::local::connect_pair as a free function in
boost::corosio. POSIX uses socketpair(); Windows performs a
private bind/listen/accept on the caller thread paired with a
connect on a short-lived worker thread, so the caller's
io_context is never driven.

Returns std::error_code (noexcept). Stream and POSIX-only
datagram overloads. native_local_stream_socket<Backend> slices
to the base parameter; assign() routes through the backend
service.

Replaces the test-only make_local_*_pair helpers from 4b952ec;
tests, benchmarks, and the user guide are migrated.
…hang

The auxiliary wait reactor blocked in WSAPoll(-1, infinite), relying
entirely on the self-pipe wakeup. wake_self() coalesces wakes via the
wake_pending_ flag and ignores send()'s return value, so a failed or
lost wakeup leaves wake_pending_ stuck true: every subsequent wake is
coalesced away and the reactor never re-checks pending_register_ /
pending_cancel_ / stop_. A newly registered wait fd then never enters
the poll set and its readiness is never detected, hanging ioc.run()
forever.

This surfaced as Windows coverage-build (gcc + gcov) timeouts in the
local_stream_socket.iocp, native.local_stream_socket.iocp, and
wait.iocp suites, whose newly enabled local-stream-on-IOCP tests
exercise acceptor wait readiness through the reactor. The heavy gcov
instrumentation widens the timing window; the regular (clang/msvc) CI
and uninstrumented builds pass.

Use a bounded 500 ms WSAPoll timeout as a safety net so a missed
wakeup costs at most one poll interval of latency instead of a
permanent hang. This mirrors the existing 500 ms GQCS safety timeout
in win_scheduler.
temp_socket_dir's destructor used std::filesystem::remove_all() on a
directory holding a bound AF_UNIX socket file. remove_all() stats each
entry via symlink_status(), which on Windows libstdc++ opens the file
with CreateFileW; opening an AF_UNIX socket file hangs in ZwCreateFile,
deadlocking teardown. This timed out the MinGW gcov coverage build
(1500s) in local_stream_socket.iocp, native.local_stream_socket.iocp
and wait.iocp -- the only suites that bind AF_UNIX paths. MSVC STL
stats without opening the file, so the regular CI and non-coverage
builds were unaffected; the wait-reactor WSAPoll change in ca33581
addressed an unrelated subsystem (the hang is in test teardown).

Unlink the socket file with std::remove (deletes by name without
opening it) before remove_all walks the now-empty directory. Portable,
no platform branch.
…psed

run_one_until wrapped wait_one in a `while (now < abs_time)` loop, but
the "no outstanding work -> stop" logic lives inside wait_one. When the
deadline had already passed at the first loop check, the body was
skipped, wait_one was never called, and the function returned 0 with
stopped() == false, violating the documented contract.

Rewrite the loop to always issue at least one wait_one, clamping the
relative timeout to [0, 1s] and rechecking the deadline at the bottom.
This also prevents a negative timeout from mapping to an indefinite
block when outstanding work is present.

Surfaced as a flaky testRunOneFor failure under valgrind, where the
thread can be preempted past a 10ms deadline before the first check.
Add deterministic regression tests using an already-elapsed deadline.
Add test/** to the code-coverage workflow path filter so test-only
pushes to master/develop refresh the coverage badges. Previously the
reported score could drift until an unrelated src/ or include/ change
triggered a rebuild.
@cppalliance-bot
Copy link
Copy Markdown

cppalliance-bot commented Jun 2, 2026

An automated preview of the documentation is available at https://265.corosio.prtest3.cppalliance.org/index.html

If more commits are pushed to the pull request, the docs will rebuild at the same URL.

2026-06-02 18:43:41 UTC

sgerbino added 2 commits June 2, 2026 20:36
Add a strict total ordering to endpoint so it can be used as a key in
std::map and std::set without a custom comparator. Endpoints order by
address family (IPv4 before IPv6), then address value, then port,
consistent with operator==.

Drop the now-redundant explicit operator!=; C++20 synthesizes it from
operator==, matching local_endpoint's comparison surface.

Closes #258
Add unit tests for local_endpoint's operator== and operator<=>, which
were previously untested public API. Covers empty-first ordering,
prefix-before-extension, abstract-socket sorting (leading null byte),
the path-too-long boundary, and use as a std::map key.
@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 2, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 77.78%. Comparing base (bb4c596) to head (dcda198).
⚠️ Report is 227 commits behind head on master.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #265      +/-   ##
==========================================
- Coverage   80.82%   77.78%   -3.05%     
==========================================
  Files          61       96      +35     
  Lines        5268     7256    +1988     
  Branches        0     1769    +1769     
==========================================
+ Hits         4258     5644    +1386     
- Misses       1010     1102      +92     
- Partials        0      510     +510     
Files with missing lines Coverage Δ
include/boost/corosio/cancel.hpp 100.00% <ø> (ø)
include/boost/corosio/detail/buffer_param.hpp 100.00% <ø> (ø)
...clude/boost/corosio/detail/cancel_at_awaitable.hpp 100.00% <ø> (ø)
include/boost/corosio/detail/dispatch_coro.hpp 100.00% <ø> (ø)
include/boost/corosio/detail/intrusive.hpp 100.00% <ø> (ø)
include/boost/corosio/detail/scheduler.hpp 100.00% <ø> (ø)
include/boost/corosio/detail/scheduler_op.hpp 91.66% <ø> (ø)
include/boost/corosio/detail/thread_local_ptr.hpp 100.00% <ø> (ø)
include/boost/corosio/detail/thread_pool.hpp 91.66% <ø> (ø)
include/boost/corosio/detail/timeout_coro.hpp 91.89% <ø> (ø)
... and 53 more

... and 58 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update bb4c596...dcda198. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@sgerbino sgerbino merged commit dcda198 into master Jun 2, 2026
85 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants