Skip to content

Coverage - #347

Merged
sgerbino merged 29 commits into
cppalliance:developfrom
sgerbino:pr/coverage-tier1
Sep 6, 2026
Merged

Coverage#347
sgerbino merged 29 commits into
cppalliance:developfrom
sgerbino:pr/coverage-tier1

Conversation

@sgerbino

@sgerbino sgerbino commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

@cppalliance-bot

cppalliance-bot commented Sep 6, 2026

Copy link
Copy Markdown

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

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

2026-09-06 03:29:28 UTC

@cppalliance-bot

cppalliance-bot commented Sep 6, 2026

Copy link
Copy Markdown

GCOVR code coverage report https://347.corosio.prtest3.cppalliance.org/gcovr/index.html
LCOV code coverage report https://347.corosio.prtest3.cppalliance.org/genhtml/index.html
Coverage Diff Report https://347.corosio.prtest3.cppalliance.org/diff-report/index.html

Build time: 2026-09-06 03:43:47 UTC

Service shutdown closes impls one at a time, so a queued pipe write
can execute against a reader the same shutdown already closed. The
kernel raises thread-directed SIGPIPE at the next kernel entry, which
kills any process that has not ignored the signal, even though the
CQE already reports EPIPE. Block and consume the signal around the
close and ring-teardown paths that can run such a write.
…aths

Coverage analysis surfaced reactor paths no execution can reach: the
per-descriptor cancel-pending flags nothing ever sets, the datagram
connect EINPROGRESS parking a datagram connect never enters, and the
private-queue drains -- along with the do_one check that guarded them --
that work_cleanup and task_cleanup make dead by splicing the private
queue to the global queue after every handler and every reactor pass.
The cleanup guards' own null-context branches are dead too: do_one is
their only source of a context and always passes a live frame. Remove
them rather than carry dead branches.
…arms

A cpp_new arm makes the nth allocation on the armed thread report
bad_alloc through replaced global allocation functions; no
interposition is needed, so the entry is live on every platform.
Covers the pool's spawn-allocation recovery, its refusal propagation
through the file service, and select's registry-growth ENOMEM arm.
The timer rearm and server launch-frame guards sit behind pooled or
boundary allocations no deterministic ordinal reaches; their sweeps
exercise robustness and the arms stay on the residual list.
pthread_create reports through its return value, so the shadow hands
back the armed error directly and std::thread construction surfaces it
as system_error. The pool's first post then reports the refusal
through the operation, leaves no latched state, and the next post
spawns the worker normally.
Shadow the OpenSSL entry points the TLS engine calls -- the credential
BIOs, context and session allocation, session reset, hostname pinning,
and input staging -- with opaque C signatures that need no OpenSSL
headers. Arming each drives the engine's refusal arms: the credential
decoder, the poisoned-cache stickiness, the reset and hostname-setup
refusals, and the staging-full retry.

The entries are gated to Linux, where the coverage badge is measured;
elsewhere the RTLD_NEXT lookup has nothing to bind and hook_is_live
reports them not live so the TLS fault suite skips.
Windows spawns its threads through _beginthreadex in the CRT, not
pthread_create, so the POSIX thread entry never fires there. Patch the
CRT's import thunk and expose the same fault, then cover the one place
it surfaces cleanly under a bounded run: the wait reactor's first wait
spawns its WSAPoll thread, and a spawn refusal must complete the wait
with resource_unavailable_try_again rather than hang.
uring_sqe_full clamps at ring creation; uring_sq_fill exhausts a
normally sized ring at a chosen moment instead. While armed, the next
liburing call pushes the user-side tail to capacity — reversibly, with
submit a no-op so nothing fake reaches the kernel — and the first call
after the scope dies restores it. Covers ring construction tearing
down and throwing when the wakeup poll cannot get an SQE, best-effort
cancellation when the queue stays full through its flush-and-retry,
the multishot arm failure path, the terminated-poll re-arms for the
wakeup eventfd and the signal pipe (in a run and in the teardown
drain), and the re-arm a terminated accept multishot posts. Composed
arms taught the shadows to rewrite visible completions even while the
fill holds the queue shut.
Each exclusion states why the arm cannot execute: callers pre-check,
eager validation upstream, vtable completeness, a second net behind a
layer that reports first, or an interface no path on the platform
drives. An exclusion is a reviewed assertion of unreachability, not a
waiver; deleting the arm was preferred wherever an interface or a
platform twin did not require it to stay.
The engine's OpenSSL calls leave the corosio_openssl DLL through its
import table; collect_modules already snapshots that satellite, so
adding the OpenSSL entry points to the Windows hook table lets the
patcher intercept them. Link the engine into the CMake Windows fault
target (the coverage legs are CMake) and un-gate the TLS fault suite
for Windows; a build without the engine loaded leaves the hooks
unbound and the suite skips via hook_is_live.
The OpenSSL shadows were confined to Linux because the main-library
rebinder could not reach them: the engine's calls leave through the
libboost_corosio_openssl satellite dylib, not the main image. Give the
satellite its own rebinding pass, keyed off its dyld image index, that
resolves each real entry through dlsym and rewrites the matching import
slots.

The pass is fail-soft: an entry that cannot be resolved or rebound is
left not live, so the TLS fault suite skips it rather than any suite in
the binary dying. The main pass now skips the OpenSSL names and the
satellite pass owns their census_live.
do_one, run_task, and the work_cleanup / task_cleanup guards all receive
the running thread's context frame, which is never null: do_one is only
ever entered with a live frame from run/run_one/poll. Typing it as a
reference makes that invariant hold in the type system rather than by
convention, so no caller can pass null and no guard needs a null check.
The nullable lookup, reactor_find_context, keeps its pointer.
testReleaseDropsPreacceptedConnections re-binds the same acceptor to an
ephemeral port and depends on it differing from the released one so the
accept can tell the new listener from the old. SO_REUSEADDR lets the
kernel hand the just-freed port straight back, so retry the ephemeral
bind until it lands on a different port instead of asserting the first
result is different.
The IPv6 leave-group option's level, name, size, and byte layout were
only reachable through a live multicast join, which the coverage host
cannot route, so those pure accessors went unmeasured. Drive them
directly against the join-group option's matching traits.
The native stream-file and random-access-file awaitables carry the same
resume-time stop check as the sockets, and the acceptors' accept()->
socket overload has its own; none were driven with a stopped token.
Add a file resume-cancel case and exercise the socket-returning accept
alongside the existing accept-into-peer arm.
@sgerbino
sgerbino merged commit dd75dfe into cppalliance:develop Sep 6, 2026
42 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Beast2 Sep 6, 2026
@sgerbino
sgerbino deleted the pr/coverage-tier1 branch September 6, 2026 04:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants