Skip to content

rewrite(phase2): port watchdog + build allocation fault-injection seam, ported from c-RCP - #133

Merged
SoundMatt merged 1 commit into
rewrite/v3-from-c-rcpfrom
phase2/watchdog-alloc-seam
Aug 21, 2026
Merged

SoundMatt merged 1 commit into
rewrite/v3-from-c-rcpfrom
phase2/watchdog-alloc-seam

Conversation

@SoundMatt

Copy link
Copy Markdown
Owner

Part of the v3.0.0 ground-up rewrite (#129, ROADMAP.md Phase 17). First batch of Phase 2, alongside e2e/lifecycle (separate PR).

watchdog.hpp

Investigation established cpp-RCP's StreamWatchdog/Manager is not a stale transliteration target — it's a deliberate, already-shipped v2.10.0 design with its own REQ-WDG-001..008 catalog that doesn't map 1:1 to c-RCP's REQ-WDG-001..012. c-RCP's own watchdog has since evolved to a background-thread design that would contradict this codebase's "no clock/thread of its own" convention and break sim.hpp/mdns.hpp's dependents — correctly not ported. Treated as a content audit instead. One genuine gap found and fixed: the c-RCP-17 fixed-capacity conversion (RCP_WATCHDOG_MAX_STREAMS/MAX_CALLBACKS, both 16) had never landed here — Manager used unbounded std::map/std::vector, unlike every other Phase 17 module. Converted to fixed std::array-backed storage; register_stream/subscribe now return std::error_code on overflow instead of silently discarding it.

alloc.hpp (NEW)

cpp-RCP had zero allocation-failure-injection capability. c-RCP's own mechanism (a global function-pointer hook table) is deliberately not ported literally — it's exactly the pattern c-RCP itself needed an access-control retrofit for later (issue #600/PR #615). Built a small, instance-owned FaultInjector instead, matching faultinject.hpp's existing idiom. Phase 1's fixed-capacity modules were checked first and found not to need it (their overflow paths never touch the heap); wired it into loan.hpp's BufferPool::loan() cache-miss branch instead — its one real heap allocation — as a genuine demonstration, optional/backward-compatible.

REQ-ALLOC-* (11 in c-RCP): none transfer as written (documented breakdown in the header) — the one concept that does, deterministic allocation-failure testability, is what FaultInjector provides.

Call site touched: sim.hpp's Simulator::register_stream widened void→std::error_code to propagate the new capacity result (no break, existing callers discard it as a statement).

test_watchdog: 12→15 cases. test_alloc (new): 13 cases.

Verified

Full tree rebuilds clean (223/223 targets), 56/56 ctest suites pass (100%) — independently re-verified from a fresh build.

Tracked at #129.

…m, ported from c-RCP

Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17),
branch rewrite/v3-from-c-rcp. First batch of Phase 2, alongside e2e/
lifecycle (separate PR).

watchdog.hpp: investigation established cpp-RCP's StreamWatchdog/Manager
is not a stale transliteration target -- it's a deliberate, already-
shipped v2.10.0 design (ROADMAP.md milestone 54) with its own
REQ-WDG-001..008 catalog that doesn't correspond 1:1 to c-RCP's
REQ-WDG-001..012. c-RCP's own watchdog has since evolved to a
background-thread rcp_watchdog_keeper_t (a later c-RCP redesign, not
what this codebase's design was ever meant to mirror) -- porting that
would contradict this codebase's own stated 'no clock/thread of its
own' convention and break sim.hpp/mdns.hpp's existing dependents.
Treated as a content audit instead, mapping each of c-RCP's 12
REQ-WDG-* against what transfers vs. doesn't. One genuine gap found and
fixed: the c-RCP-17 fixed-capacity conversion (RCP_WATCHDOG_MAX_STREAMS
/RCP_WATCHDOG_MAX_CALLBACKS, both 16) had never been ported -- Manager
used unbounded std::map/std::vector, unlike every other Phase 17
module. Converted to fixed std::array-backed storage with swap-removal
(matching loan.hpp's free-list technique); register_stream/subscribe
now return std::error_code (new stream_capacity_exceeded/
callback_capacity_exceeded) instead of silently discarding overflow.

alloc.hpp (NEW): cpp-RCP had zero allocation-failure-injection
capability -- confirmed zero try/catch(std::bad_alloc) anywhere in the
tree. c-RCP's own mechanism (a global rcp_alloc_set_hooks() function-
pointer table) is deliberately NOT ported literally -- it's exactly the
pattern that needed a later access-control retrofit in c-RCP itself
(issue #600/PR #615, rcp_alloc_lock_hooks()) after any caller could
silently redirect the allocator backing safety code. Built a small,
instance-owned rcp::alloc::FaultInjector instead (mutex-guarded,
arm(count)/should_fail()), matching faultinject.hpp's existing idiom.
Phase 1's fixed-capacity modules (request/fragment/respqueue/loan) were
checked first and found to not need it -- their capacity-exceeded paths
are std::array-backed and never touch the heap, so they're already
directly testable and can't be meaningfully fault-injected. Wired
FaultInjector into loan.hpp's BufferPool::loan() cache-miss branch
instead (its one real heap allocation), as a genuine, non-artificial
demonstration it composes with a real caller -- optional constructor
parameter, defaulted nullptr, fully backward-compatible.

REQ-ALLOC-* (11 in c-RCP) breakdown, documented in alloc.hpp's header:
none transfer as written -- 001/002/010/011 describe the global hook
table this design deliberately lacks; 003-006 describe C's four
separate allocator entry points, no C++ equivalent without a global
operator new override; 007-009 (lock/unlock/query) exist only to
retrofit access control onto a table that doesn't exist here. The one
concept that transfers -- deterministic, portable allocation-failure
testability -- is what FaultInjector::should_fail() provides.

Call sites touched: sim.hpp's Simulator::register_stream widened from
void to std::error_code to propagate the new capacity-exceeded result
(existing callers discard it as a statement, no break).

test_watchdog: 12 -> 15 test cases, 137 assertions.
test_alloc (new): 13 test cases, 98 assertions.

Verified: full tree rebuilds clean (223/223 targets), 56/56 ctest
suites pass (100%) -- independently re-verified from a fresh build, not
just the porting agent's own report.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com>
@SoundMatt
SoundMatt merged commit 2d976af into rewrite/v3-from-c-rcp Aug 21, 2026
22 checks passed
@SoundMatt
SoundMatt deleted the phase2/watchdog-alloc-seam branch August 21, 2026 19:51
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.

1 participant