From 8f85437610589263647d44c2c2e7a6a8fad79609 Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Wed, 27 May 2026 18:09:40 -0400 Subject: [PATCH 01/32] docs: add rrr Rust DSL migration plan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 0 of the goal to gradually rewrite rrr's C++ source files as Rust DSL and use the rusty-cpp transpiler to generate the C++ that compiles into librrr.a. The .rs becomes the source of truth; the .gen.cppm is a build artifact. The doc defines: - Per-iteration protocol (read .cpp → author .rs → transpile → diff → wire into CMake → verify build/borrow-check/tests → commit). - Stop rules (3 consecutive blockers). - Per-phase progress log: pilot (errors.cpp), 8 leaf files, 5 medium files, then a decision point on the large files. Transpiler is built and smoke-tested. The pilot starts in the next commit. --- docs/dev/rrr_rust_dsl_migration_plan.md | 99 +++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 docs/dev/rrr_rust_dsl_migration_plan.md diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md new file mode 100644 index 000000000..b8dc0dd34 --- /dev/null +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -0,0 +1,99 @@ +# rrr Rust DSL Migration Plan + +## Goal + +Rewrite C++ source files in `src/rrr/` as Rust DSL; use the rusty-cpp +transpiler at `third-party/rusty-cpp/transpiler/` to generate the C++ +that compiles into `librrr.a`. The `.rs` file becomes the source of +truth; the generated `.gen.cppm` is the build artifact. + +Each migration must preserve: +- **Build**: rrr static lib + every dependent test/binary link clean. +- **Behavior**: existing GTest binaries pass; representative + Docker-CI shard tests still pass at phase boundaries. +- **Safety**: `borrow_check_rrr` stays clean. + +## Tools + +- Transpiler binary: + `third-party/rusty-cpp/target/release/rusty-cpp-transpiler` +- Build: `cd third-party/rusty-cpp/transpiler && cargo build --release` +- Invocation (single file): + ``` + ./third-party/rusty-cpp/target/release/rusty-cpp-transpiler \ + src/rrr/.rs -o src/rrr/.gen.cppm -m rrr. + ``` +- Both `.rs` AND `.gen.cppm` get checked in. Consumers don't need the + transpiler to build; the `.rs` documents the source of truth. + +## Per-iteration protocol + +1. **Pick** the next unchecked item from the Progress log. +2. **Read** the original `.cpp`. Inventory exported symbols and + external imports. +3. **Author** `.rs` next to the original. Keep symbol names, module + name, and exported surface identical. +4. **Transpile** to `.gen.cppm`. +5. **Hand-diff** `.gen.cppm` against the original `.cpp`. Note any + semantically suspicious divergence in the iteration commit message. +6. **Wire into build**: swap the `.cpp` for `.gen.cppm` in + `src/rrr/CMakeLists.txt` (`RRR_MODULE_SRC` and `RRR_BORROW_SRC`). + Delete the original `.cpp`. Stage the `.rs` and `.gen.cppm`. +7. **Verify**: + - `cmake --build build_clang21 --target rrr -j32` clean + - `cmake --build build_clang21 --target borrow_check_rrr -j32` + clean + - Run the GTest binaries covering this file (e.g. test_marshal + for `misc/marshal.cpp`). Note which tests cover what in the + iteration commit message. +8. **Commit**: one file per commit. Message records: file migrated, + LOC delta on `.cpp` → `.rs`, transpiler quirks observed, tests + that passed. +9. **Tick** the Progress log: `- [x] — commit , `. + +## Stop rules + +- 3 consecutive blockers (transpiler can't lower the file) → halt, + file rusty-cpp issues for each blocker, return to other work. +- Behavior or borrow-check regression that isn't an obvious transpiler + bug → revert the migration commit and add a `[blocked]` row instead + of `[x]`. + +## Open questions / transpiler quirks observed during smoke-test + +- Transpiler emits `` (etc.) in the generated `.gen.cppm` + prelude. Per user preference, we use `` style. This is + a transpiler-side fix to upstream — defer until it actually + blocks something. +- Generated module name is from the `-m` flag, not inferred from path. + +## Progress log + +### Phase 0 — Tooling +- [x] Build transpiler release binary +- [x] Smoke-test transpiler on a toy file +- [x] Author this plan doc + +### Phase 1 — Pilot +- [ ] `src/rrr/rpc/errors.cpp` (~142 LOC, enums + pure switch tables) + +### Phase 2 — Leaf files +- [ ] `src/rrr/base/debugging.cpp` +- [ ] `src/rrr/misc/rand.cpp` +- [ ] `src/rrr/base/strop.cpp` +- [ ] `src/rrr/rpc/request_options.cpp` +- [ ] `src/rrr/rpc/reconnect_policy.cpp` +- [ ] `src/rrr/rpc/circuit_breaker.cpp` +- [ ] `src/rrr/misc/dball.cpp` +- [ ] `src/rrr/misc/cpuinfo.cpp` + +### Phase 3 — Medium files +- [ ] `src/rrr/base/logging.cpp` +- [ ] `src/rrr/rpc/idempotency.cpp` +- [ ] `src/rrr/rpc/completion_tracker.cpp` +- [ ] `src/rrr/rpc/heartbeat.cpp` +- [ ] `src/rrr/rpc/connection_metrics.cpp` + +### Phase 4 — Decision point +- [ ] Tally blocker count + LOC delta. Decide whether to continue + into the large files (client/server/marshal/reactor). From d49079adc6eba82fe25033493c42a7edab11fb3e Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Wed, 27 May 2026 18:23:28 -0400 Subject: [PATCH 02/32] =?UTF-8?q?rrr/basetypes:=20pilot=20inline=20Rust=20?= =?UTF-8?q?DSL=20=E2=80=94=20SparseInt::val=5Fsize?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1 pilot of the rrr Rust DSL migration. The body of `SparseInt::val_size(i64) -> size_t` is reauthored as an inline Rust DSL block inside basetypes.cpp; the rusty-cpp transpiler's `inline-rust --rewrite` populates the matching `/*RUSTYCPP:GEN-BEGIN id=basetypes.1 ... GEN-END*/` block right after with the C++ equivalent. The original member method now delegates to the generated free helper `sparse_int_val_size_impl(i64) -> u64`. The C++ surface is unchanged — every caller (SparseInt::buf_size, v32::val_size, v64::val_size) sees the same signature. The compiler never sees the Rust source (RUSTYCPP_RUST is undefined at build time); it compiles only the GEN block. The Rust is the developer- facing source of truth. This commit also revises docs/dev/rrr_rust_dsl_migration_plan.md to: - swap the per-iteration protocol from full-file translation (.rs → .gen.cppm) to inline mode (#if RUSTYCPP_RUST blocks in the existing .cpp). The full-file approach diverged on enum casing and return types; inline mode keeps the public surface intact. - record the v0 finding as a paragraph in the Goal section. - tick the pilot row. Verification: - `cmake --build build_clang21 --target rrr -j32` clean - `cmake --build build_clang21 --target borrow_check_rrr_borrow_basetypes -j32` clean - `build_clang21/test_marshal` — 23/23 PASSED (the SparseInt encoding-boundary suite exercises val_size on each branch) Transpiler quirks observed (logged in plan doc): - GEN block ids are auto-assigned .N — supplying your own `id=...` is ignored. - Numeric comparisons get wrapped in `rusty::detail::deref_if_pointer_like(...)`; harmless for primitive args. Co-Authored-By: Claude Opus 4.7 --- docs/dev/rrr_rust_dsl_migration_plan.md | 97 ++++++++++++++++--------- src/rrr/base/basetypes.cpp | 55 +++++++++++--- 2 files changed, 106 insertions(+), 46 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index b8dc0dd34..2925853e5 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -2,10 +2,13 @@ ## Goal -Rewrite C++ source files in `src/rrr/` as Rust DSL; use the rusty-cpp -transpiler at `third-party/rusty-cpp/transpiler/` to generate the C++ -that compiles into `librrr.a`. The `.rs` file becomes the source of -truth; the generated `.gen.cppm` is the build artifact. +Gradually migrate C++ logic in `src/rrr/` to **inline Rust DSL** blocks +inside the existing `.cpp` files. Each rewritten function (or set of +functions) sits inside a `#if RUSTYCPP_RUST ... #endif` block; the +rusty-cpp transpiler regenerates a matching `/*RUSTYCPP:GEN-BEGIN ... +END*/` block immediately after with the C++ equivalent. The C++ +compiler only ever sees the GEN block (RUSTYCPP_RUST is undefined at +build time); the Rust source is the developer-facing source of truth. Each migration must preserve: - **Build**: rrr static lib + every dependent test/binary link clean. @@ -13,43 +16,55 @@ Each migration must preserve: Docker-CI shard tests still pass at phase boundaries. - **Safety**: `borrow_check_rrr` stays clean. +Earlier `.rs → .gen.cppm` full-file translation approach (Phase 1 +v0) was abandoned because the transpiler exports Rust-shaped APIs +(PascalCase variants, `std::string_view` returns, `const auto&` +params, factory-style enum exports) which diverge from rrr's +established `SCREAMING_SNAKE` / `const char*` conventions and would +have forced consumer-wide edits. Inline mode bypasses that by +keeping the C++ surface unchanged. + ## Tools - Transpiler binary: `third-party/rusty-cpp/target/release/rusty-cpp-transpiler` - Build: `cd third-party/rusty-cpp/transpiler && cargo build --release` -- Invocation (single file): +- Inline-mode invocation (regenerate GEN blocks): + ``` + ./third-party/rusty-cpp/target/release/rusty-cpp-transpiler \ + inline-rust --rewrite --files src/rrr/.cpp + ``` +- Inline-mode invocation (CI check that GEN block matches Rust): ``` ./third-party/rusty-cpp/target/release/rusty-cpp-transpiler \ - src/rrr/.rs -o src/rrr/.gen.cppm -m rrr. + inline-rust --check --files src/rrr/.cpp ``` -- Both `.rs` AND `.gen.cppm` get checked in. Consumers don't need the - transpiler to build; the `.rs` documents the source of truth. ## Per-iteration protocol 1. **Pick** the next unchecked item from the Progress log. -2. **Read** the original `.cpp`. Inventory exported symbols and - external imports. -3. **Author** `.rs` next to the original. Keep symbol names, module - name, and exported surface identical. -4. **Transpile** to `.gen.cppm`. -5. **Hand-diff** `.gen.cppm` against the original `.cpp`. Note any - semantically suspicious divergence in the iteration commit message. -6. **Wire into build**: swap the `.cpp` for `.gen.cppm` in - `src/rrr/CMakeLists.txt` (`RRR_MODULE_SRC` and `RRR_BORROW_SRC`). - Delete the original `.cpp`. Stage the `.rs` and `.gen.cppm`. +2. **Pick a function** inside the target `.cpp` that's safe to + rewrite — pure primitive in/out first; std types later as the + transpiler proves itself. +3. **Author** an `#if RUSTYCPP_RUST ... #endif` block immediately + above the function, with the Rust DSL inside. +4. **Rewrite the C++ side** to delegate to the inline-Rust helper + (one-line forwarder, or full body replacement if signatures match). +5. **Run** `inline-rust --rewrite --files `. The tool + adds/updates a `/*RUSTYCPP:GEN-BEGIN id=... rust_sha256=.../*` ... + `/*RUSTYCPP:GEN-END id=...*/` block right after the `#endif`, + containing the generated C++. +6. **Hand-diff** the GEN block against the original C++ body. Look + for: missing branches, off-by-ones, type narrowing, unexpected + `rusty::detail::*` wrapping. Note divergence in the commit msg. 7. **Verify**: - - `cmake --build build_clang21 --target rrr -j32` clean - - `cmake --build build_clang21 --target borrow_check_rrr -j32` - clean - - Run the GTest binaries covering this file (e.g. test_marshal - for `misc/marshal.cpp`). Note which tests cover what in the - iteration commit message. -8. **Commit**: one file per commit. Message records: file migrated, - LOC delta on `.cpp` → `.rs`, transpiler quirks observed, tests - that passed. -9. **Tick** the Progress log: `- [x] — commit , `. + - `cmake --build build_clang21 --target rrr -j32` clean. + - `cmake --build build_clang21 --target + borrow_check_rrr_borrow_ -j32` clean. + - Run the GTest binaries covering this function. +8. **Commit**: one functional unit per commit. Message records which + function, LOC delta, transpiler quirks observed, tests passed. +9. **Tick** the Progress log: `- [x] :: — commit `. ## Stop rules @@ -59,13 +74,19 @@ Each migration must preserve: bug → revert the migration commit and add a `[blocked]` row instead of `[x]`. -## Open questions / transpiler quirks observed during smoke-test +## Open questions / transpiler quirks observed -- Transpiler emits `` (etc.) in the generated `.gen.cppm` - prelude. Per user preference, we use `` style. This is - a transpiler-side fix to upstream — defer until it actually - blocks something. -- Generated module name is from the `-m` flag, not inferred from path. +- Inline-mode auto-assigns GEN block ids as `.N`; the + developer should NOT supply an `id=...` in the placeholder. + Just open with `/*RUSTYCPP:GEN-BEGIN*/ /*RUSTYCPP:GEN-END*/` or + let the tool insert one fresh. +- Numeric comparisons get wrapped in + `rusty::detail::deref_if_pointer_like(...)`. Harmless for + primitive args (boils away at compile time) but adds visual + noise; worth raising upstream eventually. +- `i64` literals lower as bare `123` without the `LL` suffix — + fine on 64-bit platforms but watch for 32-bit gotchas if/when + rrr ever targets 32-bit. ## Progress log @@ -74,8 +95,12 @@ Each migration must preserve: - [x] Smoke-test transpiler on a toy file - [x] Author this plan doc -### Phase 1 — Pilot -- [ ] `src/rrr/rpc/errors.cpp` (~142 LOC, enums + pure switch tables) +### Phase 1 — Pilot (inline mode) +- [x] `base/basetypes.cpp::SparseInt::val_size` — split into + free helper `sparse_int_val_size_impl(i64) -> u64` + authored as inline Rust DSL; member method delegates. rrr + builds, borrow_check_rrr_borrow_basetypes clean, all 23 + `test_marshal` tests pass. ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/base/basetypes.cpp b/src/rrr/base/basetypes.cpp index 9e18f3238..c0245ecd0 100644 --- a/src/rrr/base/basetypes.cpp +++ b/src/rrr/base/basetypes.cpp @@ -239,27 +239,62 @@ size_t SparseInt::buf_size(char byte0) { } } -size_t SparseInt::val_size(i64 val) { - if (-64 <= val && val <= 63) { +// Free helper backing `SparseInt::val_size`. Authored as inline Rust +// DSL: the `#if RUSTYCPP_RUST` block is the source of truth; the +// transpiler regenerates the matching `/*RUSTYCPP:GEN-BEGIN ... END*/` +// block immediately below. See docs/dev/rrr_rust_dsl_migration_plan.md. +#if RUSTYCPP_RUST +fn sparse_int_val_size_impl(val: i64) -> u64 { + if val >= -64 && val <= 63 { return 1; - } else if (-8192 <= val && val <= 8191) { + } else if val >= -8192 && val <= 8191 { return 2; - } else if (-1048576 <= val && val <= 1048575) { + } else if val >= -1048576 && val <= 1048575 { return 3; - } else if (-134217728 <= val && val <= 134217727) { + } else if val >= -134217728 && val <= 134217727 { return 4; - } else if (-17179869184LL <= val && val <= 17179869183LL) { + } else if val >= -17179869184 && val <= 17179869183 { return 5; - } else if (-2199023255552LL <= val && val <= 2199023255551LL) { + } else if val >= -2199023255552 && val <= 2199023255551 { return 6; - } else if (-281474976710656LL <= val && val <= 281474976710655LL) { + } else if val >= -281474976710656 && val <= 281474976710655 { return 7; - } else if (-36028797018963968LL <= val && val <= 36028797018963967LL) { + } else if val >= -36028797018963968 && val <= 36028797018963967 { return 8; } else { - return 9; + 9 + } +} +#endif +/*RUSTYCPP:GEN-BEGIN id=basetypes.1 version=1 rust_sha256=5e0232658d8bb791e952ff50617cc358eff1c2e8847a09b73d303ca99bb153d3*/ +uint64_t sparse_int_val_size_impl(int64_t val); + +uint64_t sparse_int_val_size_impl(int64_t val) { + if ((rusty::detail::deref_if_pointer_like(val) >= -64) && (rusty::detail::deref_if_pointer_like(val) <= 63)) { + return static_cast(1); + } else if ((rusty::detail::deref_if_pointer_like(val) >= -8192) && (rusty::detail::deref_if_pointer_like(val) <= 8191)) { + return static_cast(2); + } else if ((rusty::detail::deref_if_pointer_like(val) >= -1048576) && (rusty::detail::deref_if_pointer_like(val) <= 1048575)) { + return static_cast(3); + } else if ((rusty::detail::deref_if_pointer_like(val) >= -134217728) && (rusty::detail::deref_if_pointer_like(val) <= 134217727)) { + return static_cast(4); + } else if ((rusty::detail::deref_if_pointer_like(val) >= -17179869184) && (rusty::detail::deref_if_pointer_like(val) <= 17179869183)) { + return static_cast(5); + } else if ((rusty::detail::deref_if_pointer_like(val) >= -2199023255552) && (rusty::detail::deref_if_pointer_like(val) <= 2199023255551)) { + return static_cast(6); + } else if ((rusty::detail::deref_if_pointer_like(val) >= -281474976710656) && (rusty::detail::deref_if_pointer_like(val) <= 281474976710655)) { + return static_cast(7); + } else if ((rusty::detail::deref_if_pointer_like(val) >= -36028797018963968) && (rusty::detail::deref_if_pointer_like(val) <= 36028797018963967)) { + return static_cast(8); + } else { + return static_cast(9); } } +/*RUSTYCPP:GEN-END id=basetypes.1*/ + +size_t SparseInt::val_size(i64 val) { + return static_cast(sparse_int_val_size_impl(val)); +} // @unsafe - reinterpret_cast + raw `char*` byte indexing. size_t SparseInt::dump(i32 val, char* buf) { From 5a3d29021b275a29627377f2ecec765123c27feb Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Wed, 27 May 2026 21:15:38 -0400 Subject: [PATCH 03/32] =?UTF-8?q?rrr/basetypes:=20inline=20Rust=20DSL=20?= =?UTF-8?q?=E2=80=94=20SparseInt::buf=5Fsize?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same pattern as the prior val_size pilot: `SparseInt::buf_size` body is reauthored as an inline Rust DSL block; the rusty-cpp transpiler's `inline-rust --rewrite` populates the matching `/*RUSTYCPP:GEN-BEGIN id=basetypes.1 ...*/` block right after with the C++ equivalent. The member method delegates to the generated free helper `sparse_int_buf_size_impl(i32) -> u64`. `byte0` is taken as i32 (not i8 / char) so the comparison literals (`0x80`, `0xC0`, ...) stay signed-positive on the Rust side; the caller masks to 8 bits via `static_cast(byte0) & 0xFF`. Preserves the pre-existing C++ behavior: `char` is interpreted as unsigned bit pattern via the `&` masks, exactly as before. The previous val_size GEN block was renumbered from `basetypes.1` to `basetypes.2` to make room for buf_size's auto-assigned `basetypes.1` (the transpiler's auto-id allocator goes in source order and doesn't check for collisions with existing GEN blocks; documented in the plan doc's Quirks section). Verification: - `cmake --build build_clang21 --target rrr -j32` clean. - `cmake --build build_clang21 --target borrow_check_rrr_borrow_basetypes -j32` clean. - `build_clang21/test_marshal` — 23/23 PASSED. Co-Authored-By: Claude Opus 4.7 --- docs/dev/rrr_rust_dsl_migration_plan.md | 33 ++++++++++---- src/rrr/base/basetypes.cpp | 59 ++++++++++++++++++++----- 2 files changed, 71 insertions(+), 21 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index 2925853e5..0c8603280 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -76,17 +76,26 @@ keeping the C++ surface unchanged. ## Open questions / transpiler quirks observed -- Inline-mode auto-assigns GEN block ids as `.N`; the - developer should NOT supply an `id=...` in the placeholder. - Just open with `/*RUSTYCPP:GEN-BEGIN*/ /*RUSTYCPP:GEN-END*/` or - let the tool insert one fresh. -- Numeric comparisons get wrapped in +- **Auto-id collisions** when adding a SECOND `#if RUSTYCPP_RUST` + block above an existing one. The tool's auto-id allocator + (`make_auto_id` in `inline_rust.rs:292`) assigns ids as + `.` and does not check whether the resulting + id already exists later in the file. Existing GEN blocks keep their + ids. Result: new block at the top wants `basetypes.1`, collides with + the existing `basetypes.1` later. Workaround when adding a new block + ahead of an existing one: rename the existing GEN id to the next + free slot (e.g. `basetypes.2`) in both GEN-BEGIN and GEN-END markers + BEFORE running `--rewrite`. The rust_sha256 inside GEN-BEGIN does + not need to change. +- **Pre-created GEN placeholders need full marker fields.** + `parse_gen_begin_marker` (`inline_rust.rs:184`) requires + `id`, `version`, and `rust_sha256` to all be present, otherwise + the marker is silently ignored and auto-id kicks in. Easiest: + don't pre-create a placeholder, let the tool insert one. +- Numeric comparisons in generated C++ are wrapped in `rusty::detail::deref_if_pointer_like(...)`. Harmless for primitive args (boils away at compile time) but adds visual - noise; worth raising upstream eventually. -- `i64` literals lower as bare `123` without the `LL` suffix — - fine on 64-bit platforms but watch for 32-bit gotchas if/when - rrr ever targets 32-bit. + noise. Worth raising upstream eventually. ## Progress log @@ -101,6 +110,12 @@ keeping the C++ surface unchanged. authored as inline Rust DSL; member method delegates. rrr builds, borrow_check_rrr_borrow_basetypes clean, all 23 `test_marshal` tests pass. +- [x] `base/basetypes.cpp::SparseInt::buf_size` — same shape: + free helper `sparse_int_buf_size_impl(i32) -> u64` (takes + i32 instead of `char` to dodge implementation-defined + signedness; caller masks to 8 bits). rrr builds, + borrow_check_rrr_borrow_basetypes clean, all 23 + `test_marshal` tests pass. ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/base/basetypes.cpp b/src/rrr/base/basetypes.cpp index c0245ecd0..14e88fe8f 100644 --- a/src/rrr/base/basetypes.cpp +++ b/src/rrr/base/basetypes.cpp @@ -217,27 +217,62 @@ class MergedEnumerator: public Enumerator { // `// @unsafe`. namespace rrr { -size_t SparseInt::buf_size(char byte0) { - if ((byte0 & 0x80) == 0) { +// Free helper backing `SparseInt::buf_size`. Authored as inline Rust +// DSL (see plan doc). `byte0` is taken as i32 here so the comparison +// literals are signed-positive; the caller masks to 8 bits. +#if RUSTYCPP_RUST +fn sparse_int_buf_size_impl(byte0: i32) -> u64 { + if (byte0 & 0x80) == 0 { return 1; - } else if ((byte0 & 0xC0) == 0x80) { + } else if (byte0 & 0xC0) == 0x80 { return 2; - } else if ((byte0 & 0xE0) == 0xC0) { + } else if (byte0 & 0xE0) == 0xC0 { return 3; - } else if ((byte0 & 0xF0) == 0xE0) { + } else if (byte0 & 0xF0) == 0xE0 { return 4; - } else if ((byte0 & 0xF8) == 0xF0) { + } else if (byte0 & 0xF8) == 0xF0 { return 5; - } else if ((byte0 & 0xFC) == 0xF8) { + } else if (byte0 & 0xFC) == 0xF8 { return 6; - } else if ((byte0 & 0xFE) == 0xFC) { + } else if (byte0 & 0xFE) == 0xFC { return 7; - } else if ((byte0 & 0xFF) == 0xFE) { + } else if (byte0 & 0xFF) == 0xFE { return 8; } else { - return 9; + 9 } } +#endif +/*RUSTYCPP:GEN-BEGIN id=basetypes.1 version=1 rust_sha256=31569640ef4bafbff3f49ae55731162cc8754c42d9107ed3c43edc8fbc3594b8*/ +uint64_t sparse_int_buf_size_impl(int32_t byte0); + +uint64_t sparse_int_buf_size_impl(int32_t byte0) { + if (((rusty::detail::deref_if_pointer_like(byte0) & static_cast(128))) == static_cast(0)) { + return static_cast(1); + } else if (((rusty::detail::deref_if_pointer_like(byte0) & static_cast(192))) == static_cast(128)) { + return static_cast(2); + } else if (((rusty::detail::deref_if_pointer_like(byte0) & static_cast(224))) == static_cast(192)) { + return static_cast(3); + } else if (((rusty::detail::deref_if_pointer_like(byte0) & static_cast(240))) == static_cast(224)) { + return static_cast(4); + } else if (((rusty::detail::deref_if_pointer_like(byte0) & static_cast(248))) == static_cast(240)) { + return static_cast(5); + } else if (((rusty::detail::deref_if_pointer_like(byte0) & static_cast(252))) == static_cast(248)) { + return static_cast(6); + } else if (((rusty::detail::deref_if_pointer_like(byte0) & static_cast(254))) == static_cast(252)) { + return static_cast(7); + } else if (((rusty::detail::deref_if_pointer_like(byte0) & static_cast(255))) == static_cast(254)) { + return static_cast(8); + } else { + return static_cast(9); + } +} +/*RUSTYCPP:GEN-END id=basetypes.1*/ + +size_t SparseInt::buf_size(char byte0) { + return static_cast( + sparse_int_buf_size_impl(static_cast(byte0) & 0xFF)); +} // Free helper backing `SparseInt::val_size`. Authored as inline Rust // DSL: the `#if RUSTYCPP_RUST` block is the source of truth; the @@ -266,7 +301,7 @@ fn sparse_int_val_size_impl(val: i64) -> u64 { } } #endif -/*RUSTYCPP:GEN-BEGIN id=basetypes.1 version=1 rust_sha256=5e0232658d8bb791e952ff50617cc358eff1c2e8847a09b73d303ca99bb153d3*/ +/*RUSTYCPP:GEN-BEGIN id=basetypes.2 version=1 rust_sha256=5e0232658d8bb791e952ff50617cc358eff1c2e8847a09b73d303ca99bb153d3*/ uint64_t sparse_int_val_size_impl(int64_t val); uint64_t sparse_int_val_size_impl(int64_t val) { @@ -290,7 +325,7 @@ uint64_t sparse_int_val_size_impl(int64_t val) { return static_cast(9); } } -/*RUSTYCPP:GEN-END id=basetypes.1*/ +/*RUSTYCPP:GEN-END id=basetypes.2*/ size_t SparseInt::val_size(i64 val) { return static_cast(sparse_int_val_size_impl(val)); From 6a9e938c3452c16e0bda2673c370d68a5fb2d34c Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Wed, 27 May 2026 21:46:51 -0400 Subject: [PATCH 04/32] =?UTF-8?q?rrr/errors:=20inline=20Rust=20DSL=20?= =?UTF-8?q?=E2=80=94=20get=5Ferror=5Fcategory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrates `get_error_category(RpcError) -> RpcErrorCategory` to an inline Rust DSL helper `rpc_error_category_code(i32) -> i32`. The C++ wrapper casts the enum to int at the boundary, so the public API (`RpcError`, `RpcErrorCategory`, and `get_error_category`'s signature) stays exactly as it was; existing callers and tests need no change. Also adds `#include ` and `#include ` to the module fragment — the generated C++ uses bare `int32_t` and `rusty::detail::deref_if_pointer_like`, neither of which was reachable through the previous includes. This is a one-time fix per migrated file (documented in the plan doc's Quirks section). Verification: - `cmake --build build_clang21 --target rrr -j32` clean - `cmake --build build_clang21 --target borrow_check_rrr_borrow_errors -j32` clean - `build_clang21/test_rpc_errors` — 19/19 PASSED - `build_clang21/test_rpc_error_integration` — 10/10 PASSED This is the first inline-Rust DSL migration in a non-basetypes file, proving the workflow generalizes across translation units. Co-Authored-By: Claude Opus 4.7 --- docs/dev/rrr_rust_dsl_migration_plan.md | 17 +++++++ src/rrr/rpc/errors.cpp | 60 ++++++++++++++++++++++--- 2 files changed, 70 insertions(+), 7 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index 0c8603280..81800a6bf 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -96,6 +96,13 @@ keeping the C++ surface unchanged. `rusty::detail::deref_if_pointer_like(...)`. Harmless for primitive args (boils away at compile time) but adds visual noise. Worth raising upstream eventually. +- **Module-fragment includes.** The generated C++ uses bare + `int32_t` / `uint64_t` and `rusty::detail::*`. If the target + `.cpp` doesn't already include `` and `` + in its global module fragment (`module;` ... `export module ...;`), + the first inline-rust block in that file will fail to compile + with errors like `'int32_t' must be declared before it is used`. + One-time fix per file. ## Progress log @@ -116,6 +123,16 @@ keeping the C++ surface unchanged. signedness; caller masks to 8 bits). rrr builds, borrow_check_rrr_borrow_basetypes clean, all 23 `test_marshal` tests pass. +- [x] `rpc/errors.cpp::get_error_category` — proves the workflow + across files. Free helper `rpc_error_category_code(i32) -> i32` + authored as inline Rust DSL; the C++ wrapper casts the enum + to int at the boundary so the `RpcError → RpcErrorCategory` + surface is preserved. Required adding `#include ` + + `#include ` to the module fragment so the + generated `int32_t` and `rusty::detail::deref_if_pointer_like` + names resolve. rrr builds, borrow_check_rrr_borrow_errors clean, + `test_rpc_errors` 19/19 + `test_rpc_error_integration` 10/10 + pass. ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/rpc/errors.cpp b/src/rrr/rpc/errors.cpp index a2dbcea70..386bb5b71 100644 --- a/src/rrr/rpc/errors.cpp +++ b/src/rrr/rpc/errors.cpp @@ -1,5 +1,8 @@ module; +#include +#include + export module rrr.errors; import std; @@ -106,14 +109,57 @@ inline const char* rpc_error_to_string(RpcError err) { } } +// Free helper backing `get_error_category`. Authored as inline Rust +// DSL: takes the raw integer code and returns the matching +// RpcErrorCategory's integer discriminant. The C++ wrapper casts at +// the boundary so the public RpcError / RpcErrorCategory surface +// is unchanged. See docs/dev/rrr_rust_dsl_migration_plan.md. +#if RUSTYCPP_RUST +fn rpc_error_category_code(code: i32) -> i32 { + if code == 0 { + return 0; // NONE + } + if code >= 100 && code < 200 { + return 1; // CONNECTION + } + if code >= 200 && code < 300 { + return 2; // PROTOCOL + } + if code >= 300 && code < 400 { + return 3; // APPLICATION + } + if code >= 400 && code < 500 { + return 4; // TIMEOUT + } + 5 // INTERNAL +} +#endif +/*RUSTYCPP:GEN-BEGIN id=errors.1 version=1 rust_sha256=e8bd412072b41c9da1042a00663b430c2ce1f79c2cea4e10532efe09fbf1c5ed*/ +int32_t rpc_error_category_code(int32_t code); + +int32_t rpc_error_category_code(int32_t code) { + if (rusty::detail::deref_if_pointer_like(code) == static_cast(0)) { + return static_cast(0); + } + if ((rusty::detail::deref_if_pointer_like(code) >= 100) && (rusty::detail::deref_if_pointer_like(code) < 200)) { + return static_cast(1); + } + if ((rusty::detail::deref_if_pointer_like(code) >= 200) && (rusty::detail::deref_if_pointer_like(code) < 300)) { + return static_cast(2); + } + if ((rusty::detail::deref_if_pointer_like(code) >= 300) && (rusty::detail::deref_if_pointer_like(code) < 400)) { + return static_cast(3); + } + if ((rusty::detail::deref_if_pointer_like(code) >= 400) && (rusty::detail::deref_if_pointer_like(code) < 500)) { + return static_cast(4); + } + return static_cast(5); +} +/*RUSTYCPP:GEN-END id=errors.1*/ + inline RpcErrorCategory get_error_category(RpcError err) { - int code = static_cast(err); - if (code == 0) return RpcErrorCategory::NONE; - if (code >= 100 && code < 200) return RpcErrorCategory::CONNECTION; - if (code >= 200 && code < 300) return RpcErrorCategory::PROTOCOL; - if (code >= 300 && code < 400) return RpcErrorCategory::APPLICATION; - if (code >= 400 && code < 500) return RpcErrorCategory::TIMEOUT; - return RpcErrorCategory::INTERNAL; + return static_cast( + rpc_error_category_code(static_cast(err))); } inline bool is_connection_error(RpcError err) { From 25e6eede61ab95d993e49296cc9af1a8d3fbcdc1 Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Wed, 27 May 2026 22:54:26 -0400 Subject: [PATCH 05/32] =?UTF-8?q?rrr/errors:=20inline=20Rust=20DSL=20?= =?UTF-8?q?=E2=80=94=20is=5Fretryable=5Ferror?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrates `is_retryable_error(RpcError) -> bool` to inline Rust DSL. Free helper `rpc_error_is_retryable(i32) -> bool`; C++ wrapper casts the enum to int. Public surface unchanged. Authored as an if-chain rather than a `match`. First draft used `match code { 102 | 103 | 104 | ... => true, _ => false }` and the transpiler emitted ~3800 lines of runtime support code (rusty::cmp, Option, proc_macro_runtime, etc.) into the GEN block — none of which then resolves against the surrounding `namespace rrr` (errors: `no template named 'Option' in namespace 'rrr::rusty'`). The if-chain lowering is the clean ~25-line path. Documented in the plan doc as the second known transpiler quirk worth raising upstream. Verification: - `cmake --build build_clang21 --target rrr -j32` clean - `cmake --build build_clang21 --target borrow_check_rrr_borrow_errors -j32` clean - `build_clang21/test_rpc_errors` — 19/19 PASSED Co-Authored-By: Claude Opus 4.7 --- docs/dev/rrr_rust_dsl_migration_plan.md | 14 +++++ src/rrr/rpc/errors.cpp | 73 +++++++++++++++++++++---- 2 files changed, 75 insertions(+), 12 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index 81800a6bf..4a3823126 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -103,6 +103,15 @@ keeping the C++ surface unchanged. the first inline-rust block in that file will fail to compile with errors like `'int32_t' must be declared before it is used`. One-time fix per file. +- **`match` lowering is heavyweight.** Even for simple + match-on-integer patterns, the transpiler emits ~3800 lines of + runtime support (`rusty::cmp::*`, `Option`, + `proc_macro_runtime`, etc.) into the GEN block, which then fails + to compile against rrr's namespace setup (`error: no template + named 'Option' in namespace 'rrr::rusty'`). Workaround for now: + rewrite small enum-classifier matches as if-chains; the if-chain + lowering is the clean ~25-line path. Worth raising upstream as + "lightweight lowering for primitive match-on-int". ## Progress log @@ -133,6 +142,11 @@ keeping the C++ surface unchanged. names resolve. rrr builds, borrow_check_rrr_borrow_errors clean, `test_rpc_errors` 19/19 + `test_rpc_error_integration` 10/10 pass. +- [x] `rpc/errors.cpp::is_retryable_error` — free helper + `rpc_error_is_retryable(i32) -> bool` authored as a Rust + if-chain (not `match`! see quirks). C++ wrapper casts. rrr + builds, borrow_check_rrr_borrow_errors clean, + `test_rpc_errors` 19/19 pass. ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/rpc/errors.cpp b/src/rrr/rpc/errors.cpp index 386bb5b71..2e7f50c37 100644 --- a/src/rrr/rpc/errors.cpp +++ b/src/rrr/rpc/errors.cpp @@ -170,19 +170,68 @@ inline bool is_timeout_error(RpcError err) { return get_error_category(err) == RpcErrorCategory::TIMEOUT; } -inline bool is_retryable_error(RpcError err) { - switch (err) { - case RpcError::CONNECTION_RESET: - case RpcError::NETWORK_UNREACHABLE: - case RpcError::HOST_UNREACHABLE: - case RpcError::CONNECT_TIMEOUT: - case RpcError::REQUEST_TIMEOUT: - case RpcError::RESPONSE_TIMEOUT: - case RpcError::SERVICE_UNAVAILABLE: - return true; - default: - return false; +// Free helper backing `is_retryable_error`. Returns true if `code` is +// in the retryable subset (codes drawn from the original +// CONNECTION_RESET / NETWORK_UNREACHABLE / HOST_UNREACHABLE / +// CONNECT_TIMEOUT / REQUEST_TIMEOUT / RESPONSE_TIMEOUT / +// SERVICE_UNAVAILABLE enum values). Authored as inline Rust DSL. +#if RUSTYCPP_RUST +fn rpc_error_is_retryable(code: i32) -> bool { + if code == 102 { + return true; // CONNECTION_RESET + } + if code == 103 { + return true; // NETWORK_UNREACHABLE + } + if code == 104 { + return true; // HOST_UNREACHABLE + } + if code == 301 { + return true; // SERVICE_UNAVAILABLE + } + if code == 400 { + return true; // CONNECT_TIMEOUT + } + if code == 401 { + return true; // REQUEST_TIMEOUT + } + if code == 402 { + return true; // RESPONSE_TIMEOUT + } + false +} +#endif +/*RUSTYCPP:GEN-BEGIN id=errors.2 version=1 rust_sha256=446346dfa298d97bb982ac863ab4b19c481d2c7a45cb33a3654778fd036461f2*/ +bool rpc_error_is_retryable(int32_t code); + +bool rpc_error_is_retryable(int32_t code) { + if (rusty::detail::deref_if_pointer_like(code) == static_cast(102)) { + return true; + } + if (rusty::detail::deref_if_pointer_like(code) == static_cast(103)) { + return true; + } + if (rusty::detail::deref_if_pointer_like(code) == static_cast(104)) { + return true; + } + if (rusty::detail::deref_if_pointer_like(code) == static_cast(301)) { + return true; + } + if (rusty::detail::deref_if_pointer_like(code) == static_cast(400)) { + return true; } + if (rusty::detail::deref_if_pointer_like(code) == static_cast(401)) { + return true; + } + if (rusty::detail::deref_if_pointer_like(code) == static_cast(402)) { + return true; + } + return false; +} +/*RUSTYCPP:GEN-END id=errors.2*/ + +inline bool is_retryable_error(RpcError err) { + return rpc_error_is_retryable(static_cast(err)); } } // export namespace rrr From c0b2d6f73c764863f7f01c97c158b127d36fc4cf Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Wed, 27 May 2026 23:27:46 -0400 Subject: [PATCH 06/32] =?UTF-8?q?rrr/errors:=20inline=20Rust=20DSL=20?= =?UTF-8?q?=E2=80=94=20is=5Fconnection=5Ferror=20+=20is=5Ftimeout=5Ferror?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stamps the inline-Rust pattern over two more tiny predicates: - `is_connection_error(err)` → free helper `rpc_error_is_connection_code(i32) -> bool` (range check 100 ≤ code < 200). - `is_timeout_error(err)` → free helper `rpc_error_is_timeout_code(i32) -> bool` (range check 400 ≤ code < 500). Both equivalent to the previous `get_error_category(err) == X` form since CONNECTION/TIMEOUT are precisely those code ranges in rpc_error_category_code. C++ wrappers cast the enum to int at the boundary so the public surface is unchanged. is_retryable_error's existing GEN id renumbered from `errors.2` to `errors.4` to give the two new blocks (`errors.2`, `errors.3`) contiguous auto-ids in source order — same workaround pattern as the SparseInt::buf_size pilot. Verification: - `cmake --build build_clang21 --target rrr -j32` clean - `cmake --build build_clang21 --target borrow_check_rrr_borrow_errors -j32` clean - `build_clang21/test_rpc_errors` — 19/19 PASSED Co-Authored-By: Claude Opus 4.7 --- src/rrr/rpc/errors.cpp | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/src/rrr/rpc/errors.cpp b/src/rrr/rpc/errors.cpp index 2e7f50c37..51ee60fff 100644 --- a/src/rrr/rpc/errors.cpp +++ b/src/rrr/rpc/errors.cpp @@ -162,12 +162,44 @@ inline RpcErrorCategory get_error_category(RpcError err) { rpc_error_category_code(static_cast(err))); } +// Free helper backing `is_connection_error`. Mirrors the CONNECTION +// range from rpc_error_category_code (codes 100..200) — equivalent to +// `get_error_category(err) == CONNECTION` since CONNECTION is exactly +// that range. Authored as inline Rust DSL. +#if RUSTYCPP_RUST +fn rpc_error_is_connection_code(code: i32) -> bool { + code >= 100 && code < 200 +} +#endif +/*RUSTYCPP:GEN-BEGIN id=errors.2 version=1 rust_sha256=8b212483fae198e97688da56144b9d049ca0b858031c111a6ca98d470af16969*/ +bool rpc_error_is_connection_code(int32_t code); + +bool rpc_error_is_connection_code(int32_t code) { + return (rusty::detail::deref_if_pointer_like(code) >= 100) && (rusty::detail::deref_if_pointer_like(code) < 200); +} +/*RUSTYCPP:GEN-END id=errors.2*/ + inline bool is_connection_error(RpcError err) { - return get_error_category(err) == RpcErrorCategory::CONNECTION; + return rpc_error_is_connection_code(static_cast(err)); } +// Free helper backing `is_timeout_error`. Mirrors the TIMEOUT range +// (codes 400..500). Authored as inline Rust DSL. +#if RUSTYCPP_RUST +fn rpc_error_is_timeout_code(code: i32) -> bool { + code >= 400 && code < 500 +} +#endif +/*RUSTYCPP:GEN-BEGIN id=errors.3 version=1 rust_sha256=cea5df0d68347a7eaf1e10af39549a12418b49f0126b95862e81610734b2a1c2*/ +bool rpc_error_is_timeout_code(int32_t code); + +bool rpc_error_is_timeout_code(int32_t code) { + return (rusty::detail::deref_if_pointer_like(code) >= 400) && (rusty::detail::deref_if_pointer_like(code) < 500); +} +/*RUSTYCPP:GEN-END id=errors.3*/ + inline bool is_timeout_error(RpcError err) { - return get_error_category(err) == RpcErrorCategory::TIMEOUT; + return rpc_error_is_timeout_code(static_cast(err)); } // Free helper backing `is_retryable_error`. Returns true if `code` is @@ -201,7 +233,7 @@ fn rpc_error_is_retryable(code: i32) -> bool { false } #endif -/*RUSTYCPP:GEN-BEGIN id=errors.2 version=1 rust_sha256=446346dfa298d97bb982ac863ab4b19c481d2c7a45cb33a3654778fd036461f2*/ +/*RUSTYCPP:GEN-BEGIN id=errors.4 version=1 rust_sha256=446346dfa298d97bb982ac863ab4b19c481d2c7a45cb33a3654778fd036461f2*/ bool rpc_error_is_retryable(int32_t code); bool rpc_error_is_retryable(int32_t code) { @@ -228,7 +260,7 @@ bool rpc_error_is_retryable(int32_t code) { } return false; } -/*RUSTYCPP:GEN-END id=errors.2*/ +/*RUSTYCPP:GEN-END id=errors.4*/ inline bool is_retryable_error(RpcError err) { return rpc_error_is_retryable(static_cast(err)); From cbd8629e0b69892e75415179c615f260e5c69138 Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Wed, 27 May 2026 23:30:04 -0400 Subject: [PATCH 07/32] rrr/errors: consolidate is_connection/is_timeout helpers into one block MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tests the transpiler's multi-function-per-block lowering by merging the two predicate helpers `rpc_error_is_connection_code` and `rpc_error_is_timeout_code` into a single `#if RUSTYCPP_RUST ... #endif`. Works cleanly: the transpiler emits ONE GEN block (id=errors.2) with both forward decls at the top and both definitions below. Net file change is a small cleanup: 4 GEN blocks → 3, since errors.3 is consumed into errors.2. Verification: - `cmake --build build_clang21 --target rrr -j32` clean - `cmake --build build_clang21 --target borrow_check_rrr_borrow_errors -j32` clean - `build_clang21/test_rpc_errors` — 19/19 PASSED Plan-doc Phase 1 row updated to record both the initial two-block landing (c0b2d6f7) and this consolidation step. Co-Authored-By: Claude Opus 4.7 --- docs/dev/rrr_rust_dsl_migration_plan.md | 8 ++++++ src/rrr/rpc/errors.cpp | 34 ++++++++++--------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index 4a3823126..0c41fbede 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -147,6 +147,14 @@ keeping the C++ surface unchanged. if-chain (not `match`! see quirks). C++ wrapper casts. rrr builds, borrow_check_rrr_borrow_errors clean, `test_rpc_errors` 19/19 pass. +- [x] `rpc/errors.cpp::is_connection_error` + + `is_timeout_error` — initially landed as two separate + `#if RUSTYCPP_RUST` blocks (commit c0b2d6f7), then + consolidated into ONE block containing both Rust helpers + to test multi-fn block lowering. Transpiler handles it + cleanly: single GEN block emits both forward decls then + both definitions. rrr builds, borrow_check_rrr_borrow_errors + clean, `test_rpc_errors` 19/19 pass. ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/rpc/errors.cpp b/src/rrr/rpc/errors.cpp index 51ee60fff..f6873c297 100644 --- a/src/rrr/rpc/errors.cpp +++ b/src/rrr/rpc/errors.cpp @@ -162,42 +162,36 @@ inline RpcErrorCategory get_error_category(RpcError err) { rpc_error_category_code(static_cast(err))); } -// Free helper backing `is_connection_error`. Mirrors the CONNECTION -// range from rpc_error_category_code (codes 100..200) — equivalent to -// `get_error_category(err) == CONNECTION` since CONNECTION is exactly -// that range. Authored as inline Rust DSL. +// Free helpers backing `is_connection_error` and `is_timeout_error`. +// Mirror the CONNECTION (codes 100..200) and TIMEOUT (codes 400..500) +// ranges from rpc_error_category_code. Co-located in a single Rust +// DSL block to test the transpiler's multi-function block lowering. #if RUSTYCPP_RUST fn rpc_error_is_connection_code(code: i32) -> bool { code >= 100 && code < 200 } + +fn rpc_error_is_timeout_code(code: i32) -> bool { + code >= 400 && code < 500 +} #endif -/*RUSTYCPP:GEN-BEGIN id=errors.2 version=1 rust_sha256=8b212483fae198e97688da56144b9d049ca0b858031c111a6ca98d470af16969*/ +/*RUSTYCPP:GEN-BEGIN id=errors.2 version=1 rust_sha256=f27511053273d08c250d2b0678d3d57d430ceb1c55398f1ccd5eb96cf9955b6f*/ bool rpc_error_is_connection_code(int32_t code); +bool rpc_error_is_timeout_code(int32_t code); bool rpc_error_is_connection_code(int32_t code) { return (rusty::detail::deref_if_pointer_like(code) >= 100) && (rusty::detail::deref_if_pointer_like(code) < 200); } + +bool rpc_error_is_timeout_code(int32_t code) { + return (rusty::detail::deref_if_pointer_like(code) >= 400) && (rusty::detail::deref_if_pointer_like(code) < 500); +} /*RUSTYCPP:GEN-END id=errors.2*/ inline bool is_connection_error(RpcError err) { return rpc_error_is_connection_code(static_cast(err)); } -// Free helper backing `is_timeout_error`. Mirrors the TIMEOUT range -// (codes 400..500). Authored as inline Rust DSL. -#if RUSTYCPP_RUST -fn rpc_error_is_timeout_code(code: i32) -> bool { - code >= 400 && code < 500 -} -#endif -/*RUSTYCPP:GEN-BEGIN id=errors.3 version=1 rust_sha256=cea5df0d68347a7eaf1e10af39549a12418b49f0126b95862e81610734b2a1c2*/ -bool rpc_error_is_timeout_code(int32_t code); - -bool rpc_error_is_timeout_code(int32_t code) { - return (rusty::detail::deref_if_pointer_like(code) >= 400) && (rusty::detail::deref_if_pointer_like(code) < 500); -} -/*RUSTYCPP:GEN-END id=errors.3*/ - inline bool is_timeout_error(RpcError err) { return rpc_error_is_timeout_code(static_cast(err)); } From a62fdb5b60c2f1c295ce9412e51539ce6c096b7f Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Thu, 28 May 2026 08:49:06 -0400 Subject: [PATCH 08/32] =?UTF-8?q?rrr/reconnect=5Fpolicy:=20inline=20Rust?= =?UTF-8?q?=20DSL=20=E2=80=94=20should=5Fretry=20+=20retries=5Fexhausted?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both predicates classify the same (auto_reconnect, max_retries, retry_count) tuple, so they're a natural fit for the multi-fn block pattern proven in the previous errors.cpp commit. Authored as `reconnect_should_retry(bool, u32, u32) -> bool` and `reconnect_retries_exhausted(bool, u32, u32) -> bool` in a single `#if RUSTYCPP_RUST` block; the transpiler emits one GEN block with both forward decls and definitions. Member methods on `ReconnectCalculator` are now one-line forwarders that read the policy fields + retry counter and pass to the helpers. Public surface unchanged. First inline-Rust migration in a third file. Required adding `#include ` and `#include ` to the module fragment (same one-time-per-file fix documented in the plan doc). Verification: - `cmake --build build_clang21 --target rrr -j32` clean - `cmake --build build_clang21 --target borrow_check_rrr_borrow_reconnect_policy -j32` clean - `build_clang21/test_rpc_reconnect_policy` — 19/19 PASSED - `build_clang21/test_rpc_reconnect_integration` — 12/12 enabled PASSED (6 disabled tests skipped, unrelated) Co-Authored-By: Claude Opus 4.7 --- docs/dev/rrr_rust_dsl_migration_plan.md | 11 ++++ src/rrr/rpc/reconnect_policy.cpp | 71 +++++++++++++++++++------ 2 files changed, 67 insertions(+), 15 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index 0c41fbede..e78b20359 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -155,6 +155,17 @@ keeping the C++ surface unchanged. cleanly: single GEN block emits both forward decls then both definitions. rrr builds, borrow_check_rrr_borrow_errors clean, `test_rpc_errors` 19/19 pass. +- [x] `rpc/reconnect_policy.cpp::ReconnectCalculator::should_retry` + + `retries_exhausted` — both classify the same + `(auto_reconnect, max_retries, retry_count)` tuple; co-located + into one multi-fn `#if RUSTYCPP_RUST` block as + `reconnect_should_retry` / `reconnect_retries_exhausted`. C++ + member methods are now one-line forwarders. First migration in + a third file; first migration that uses `bool` + `u32` + parameters (vs only `i32`/`i64` before). rrr builds, + borrow_check_rrr_borrow_reconnect_policy clean, + `test_rpc_reconnect_policy` 19/19 pass, + `test_rpc_reconnect_integration` 12/12 enabled pass. ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/rpc/reconnect_policy.cpp b/src/rrr/rpc/reconnect_policy.cpp index bb4a22b70..7ab81f5c4 100644 --- a/src/rrr/rpc/reconnect_policy.cpp +++ b/src/rrr/rpc/reconnect_policy.cpp @@ -1,7 +1,8 @@ module; #include -#include +#include +#include export module rrr.reconnect_policy; @@ -57,6 +58,56 @@ struct ReconnectPolicy { } }; +// Free helpers backing `ReconnectCalculator::should_retry` and +// `ReconnectCalculator::retries_exhausted`. Both classify the same +// (auto_reconnect, max_retries, retry_count) tuple; co-located in +// a single inline Rust DSL block. +#if RUSTYCPP_RUST +fn reconnect_should_retry(auto_reconnect: bool, max_retries: u32, retry_count: u32) -> bool { + if !auto_reconnect { + return false; + } + if max_retries == 0 { + return true; + } + retry_count < max_retries +} + +fn reconnect_retries_exhausted(auto_reconnect: bool, max_retries: u32, retry_count: u32) -> bool { + if !auto_reconnect { + return true; + } + if max_retries == 0 { + return false; + } + retry_count >= max_retries +} +#endif +/*RUSTYCPP:GEN-BEGIN id=reconnect_policy.1 version=1 rust_sha256=650e4aca5bc6f4b32d2e9c3d6866cc9f1a0b77648f4283190c041999427c859c*/ +bool reconnect_should_retry(bool auto_reconnect, uint32_t max_retries, uint32_t retry_count); +bool reconnect_retries_exhausted(bool auto_reconnect, uint32_t max_retries, uint32_t retry_count); + +bool reconnect_should_retry(bool auto_reconnect, uint32_t max_retries, uint32_t retry_count) { + if (!auto_reconnect) { + return false; + } + if (rusty::detail::deref_if_pointer_like(max_retries) == static_cast(0)) { + return true; + } + return rusty::detail::deref_if_pointer_like(retry_count) < rusty::detail::deref_if_pointer_like(max_retries); +} + +bool reconnect_retries_exhausted(bool auto_reconnect, uint32_t max_retries, uint32_t retry_count) { + if (!auto_reconnect) { + return true; + } + if (rusty::detail::deref_if_pointer_like(max_retries) == static_cast(0)) { + return false; + } + return rusty::detail::deref_if_pointer_like(retry_count) >= rusty::detail::deref_if_pointer_like(max_retries); +} +/*RUSTYCPP:GEN-END id=reconnect_policy.1*/ + class ReconnectCalculator { private: const ReconnectPolicy& policy_; @@ -74,13 +125,8 @@ class ReconnectCalculator { ReconnectCalculator& operator=(ReconnectCalculator&&) = default; bool should_retry() const { - if (!policy_.auto_reconnect) { - return false; - } - if (policy_.max_retries == 0) { - return true; - } - return retry_count_.get() < policy_.max_retries; + return reconnect_should_retry( + policy_.auto_reconnect, policy_.max_retries, retry_count_.get()); } uint32_t next_delay_ms() { @@ -133,13 +179,8 @@ class ReconnectCalculator { } bool retries_exhausted() const { - if (!policy_.auto_reconnect) { - return true; - } - if (policy_.max_retries == 0) { - return false; - } - return retry_count_.get() >= policy_.max_retries; + return reconnect_retries_exhausted( + policy_.auto_reconnect, policy_.max_retries, retry_count_.get()); } }; From ddc582bd1558d5dd3d5983746e80f2cbb920a823 Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Thu, 28 May 2026 12:17:58 -0400 Subject: [PATCH 09/32] =?UTF-8?q?rrr/reconnect=5Fpolicy:=20inline=20Rust?= =?UTF-8?q?=20DSL=20=E2=80=94=20peek=5Fdelay=5Fms=20+=20next=5Fdelay=5Fms?= =?UTF-8?q?=20backoff?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a third helper `reconnect_peek_delay_ms_impl(u32, u32, f64, u32) -> u32` to the existing multi-fn block. Lowers the deterministic exponential backoff loop used by both `peek_delay_ms` and `next_delay_ms`: - First inline-Rust migration to use `f64` arithmetic. - First migration to use a `while` loop with `break`. - First migration to share one helper across two C++ call sites. Transpiler handled the lowering cleanly (~17 lines of C++ from the GEN block, no runtime-support spam). Documented one minor quirk: a primitive-typed assignment like `delay = max_delay` lowers as `delay = std::move(max_delay)` — harmless on a `double`. `peek_delay_ms` becomes a 5-line forwarder. `next_delay_ms` is refactored to share the deterministic backoff through the new helper, then applies its random_device jitter on the C++ side (random_device pulls system entropy; not a Rust-DSL candidate yet). ~15 lines of duplicated backoff math removed. Verification: - `cmake --build build_clang21 --target rrr -j32` clean - `cmake --build build_clang21 --target borrow_check_rrr_borrow_reconnect_policy -j32` clean - `build_clang21/test_rpc_reconnect_policy` — 19/19 PASSED Co-Authored-By: Claude Opus 4.7 --- docs/dev/rrr_rust_dsl_migration_plan.md | 19 ++++++ src/rrr/rpc/reconnect_policy.cpp | 85 +++++++++++++++++-------- 2 files changed, 79 insertions(+), 25 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index e78b20359..61c81de96 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -112,6 +112,13 @@ keeping the C++ surface unchanged. rewrite small enum-classifier matches as if-chains; the if-chain lowering is the clean ~25-line path. Worth raising upstream as "lightweight lowering for primitive match-on-int". +- **Over-eager `std::move` on primitive locals.** In the + `peek_delay_ms` migration the transpiler emitted + `delay = std::move(max_delay)` where `max_delay` is a const + `double`. Harmless (compiles, runs identically — primitives + have no move semantics), but conceptually noisy. Same shape: + any `let x = y;` where `y` is a local seems to lower to + `std::move(y)`. ## Progress log @@ -166,6 +173,18 @@ keeping the C++ surface unchanged. borrow_check_rrr_borrow_reconnect_policy clean, `test_rpc_reconnect_policy` 19/19 pass, `test_rpc_reconnect_integration` 12/12 enabled pass. +- [x] `rpc/reconnect_policy.cpp::peek_delay_ms` + the + deterministic-backoff portion of `next_delay_ms` — added a third + helper `reconnect_peek_delay_ms_impl(u32, u32, f64, u32) -> u32` + to the existing multi-fn block. First f64 + `while`-loop + `break` + in the migration; transpiler lowered cleanly to ~17 lines of + C++ (no runtime-support spam). `next_delay_ms` now delegates the + deterministic backoff to the same helper, then applies its + random_device jitter on the C++ side (no inline Rust there + because random_device pulls system entropy). Removed ~15 lines + of duplicated backoff math in C++. rrr builds, + borrow_check_rrr_borrow_reconnect_policy clean, + `test_rpc_reconnect_policy` 19/19 pass. ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/rpc/reconnect_policy.cpp b/src/rrr/rpc/reconnect_policy.cpp index 7ab81f5c4..6aa0c2601 100644 --- a/src/rrr/rpc/reconnect_policy.cpp +++ b/src/rrr/rpc/reconnect_policy.cpp @@ -82,10 +82,38 @@ fn reconnect_retries_exhausted(auto_reconnect: bool, max_retries: u32, retry_cou } retry_count >= max_retries } + +// Deterministic exponential backoff used by both `peek_delay_ms` and the +// pure part of `next_delay_ms` (the jitter step is C++-side because it +// pulls a random sample). Multiplies `initial_delay_ms` by +// `backoff_multiplier` once per retry, clamping at `max_delay_ms`. +fn reconnect_peek_delay_ms_impl( + initial_delay_ms: u32, + max_delay_ms: u32, + backoff_multiplier: f64, + retry_count: u32, +) -> u32 { + let mut delay: f64 = initial_delay_ms as f64; + let max_delay: f64 = max_delay_ms as f64; + let mut i: u32 = 0; + while i < retry_count { + delay *= backoff_multiplier; + if delay >= max_delay { + delay = max_delay; + break; + } + i += 1; + } + if delay > max_delay { + delay = max_delay; + } + delay as u32 +} #endif -/*RUSTYCPP:GEN-BEGIN id=reconnect_policy.1 version=1 rust_sha256=650e4aca5bc6f4b32d2e9c3d6866cc9f1a0b77648f4283190c041999427c859c*/ +/*RUSTYCPP:GEN-BEGIN id=reconnect_policy.1 version=1 rust_sha256=0e4abdd3e05b5d86e56260ce54c1c801ba8e023d2f6b957f22b797873c483b6b*/ bool reconnect_should_retry(bool auto_reconnect, uint32_t max_retries, uint32_t retry_count); bool reconnect_retries_exhausted(bool auto_reconnect, uint32_t max_retries, uint32_t retry_count); +uint32_t reconnect_peek_delay_ms_impl(uint32_t initial_delay_ms, uint32_t max_delay_ms, double backoff_multiplier, uint32_t retry_count); bool reconnect_should_retry(bool auto_reconnect, uint32_t max_retries, uint32_t retry_count) { if (!auto_reconnect) { @@ -106,6 +134,24 @@ bool reconnect_retries_exhausted(bool auto_reconnect, uint32_t max_retries, uint } return rusty::detail::deref_if_pointer_like(retry_count) >= rusty::detail::deref_if_pointer_like(max_retries); } + +uint32_t reconnect_peek_delay_ms_impl(uint32_t initial_delay_ms, uint32_t max_delay_ms, double backoff_multiplier, uint32_t retry_count) { + double delay = static_cast(initial_delay_ms); + const double max_delay = static_cast(max_delay_ms); + uint32_t i = static_cast(0); + while (rusty::detail::deref_if_pointer_like(i) < rusty::detail::deref_if_pointer_like(retry_count)) { + delay *= backoff_multiplier; + if (rusty::detail::deref_if_pointer_like(delay) >= rusty::detail::deref_if_pointer_like(max_delay)) { + delay = std::move(max_delay); + break; + } + i += 1; + } + if (rusty::detail::deref_if_pointer_like(delay) > rusty::detail::deref_if_pointer_like(max_delay)) { + delay = std::move(max_delay); + } + return static_cast(delay); +} /*RUSTYCPP:GEN-END id=reconnect_policy.1*/ class ReconnectCalculator { @@ -133,17 +179,15 @@ class ReconnectCalculator { uint32_t count = retry_count_.get(); retry_count_.set(count + 1); - double delay = static_cast(policy_.initial_delay_ms); - for (uint32_t i = 0; i < count; ++i) { - delay *= policy_.backoff_multiplier; - if (delay >= static_cast(policy_.max_delay_ms)) { - delay = static_cast(policy_.max_delay_ms); - break; - } - } - - delay = std::min(delay, static_cast(policy_.max_delay_ms)); + // Deterministic exponential backoff shared with peek_delay_ms. + double delay = static_cast( + reconnect_peek_delay_ms_impl( + policy_.initial_delay_ms, + policy_.max_delay_ms, + policy_.backoff_multiplier, + count)); + // Jitter step stays C++-side (uses std::random_device). if (policy_.jitter_enabled && delay > 0) { std::random_device rd; std::uniform_real_distribution dist(0.5, 1.5); @@ -154,20 +198,11 @@ class ReconnectCalculator { } uint32_t peek_delay_ms() const { - uint32_t count = retry_count_.get(); - - double delay = static_cast(policy_.initial_delay_ms); - for (uint32_t i = 0; i < count; ++i) { - delay *= policy_.backoff_multiplier; - if (delay >= static_cast(policy_.max_delay_ms)) { - delay = static_cast(policy_.max_delay_ms); - break; - } - } - - delay = std::min(delay, static_cast(policy_.max_delay_ms)); - - return static_cast(delay); + return reconnect_peek_delay_ms_impl( + policy_.initial_delay_ms, + policy_.max_delay_ms, + policy_.backoff_multiplier, + retry_count_.get()); } void reset() { From b73307814fa5281a0a67e0efffd185189e3446e4 Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Thu, 28 May 2026 13:20:43 -0400 Subject: [PATCH 10/32] =?UTF-8?q?rrr/circuit=5Fbreaker:=20inline=20Rust=20?= =?UTF-8?q?DSL=20=E2=80=94=20circuit=5Fshould=5Fprobe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extracts the OPEN→HALF_OPEN probe-readiness check from `CircuitBreaker::allow_request` into a free helper `circuit_should_probe(now_us: u64, last_failure_us: u64, timeout_ms: u32) -> bool`. First inline-Rust migration to use `u64` parameters; transpiler lowered cleanly. Most of the rest of this file manipulates `rusty::Cell` interior- mutable state from C++ — the Rust DSL has no idiom to reach into those, so the probe-readiness arithmetic is the file's only natural candidate. Verification: - `cmake --build build_clang21 --target rrr -j32` clean - `cmake --build build_clang21 --target borrow_check_rrr_borrow_circuit_breaker -j32` clean - `build_clang21/test_rpc_circuit_breaker` — 21/21 PASSED Co-Authored-By: Claude Opus 4.7 --- docs/dev/rrr_rust_dsl_migration_plan.md | 9 ++++++++ src/rrr/rpc/circuit_breaker.cpp | 29 ++++++++++++++++++++----- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index 61c81de96..1cbf5f5d4 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -185,6 +185,15 @@ keeping the C++ surface unchanged. of duplicated backoff math in C++. rrr builds, borrow_check_rrr_borrow_reconnect_policy clean, `test_rpc_reconnect_policy` 19/19 pass. +- [x] `rpc/circuit_breaker.cpp::allow_request`'s OPEN-timeout check — + extracted as free helper + `circuit_should_probe(u64, u64, u32) -> bool`. First migration + to use `u64`. Transpiler handled cleanly. Most of the file's + methods touch `rusty::Cell` interior-mutable state which the + Rust DSL can't reach into, so this single arithmetic check is + the only candidate. rrr builds, + borrow_check_rrr_borrow_circuit_breaker clean, + `test_rpc_circuit_breaker` 21/21 pass. ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/rpc/circuit_breaker.cpp b/src/rrr/rpc/circuit_breaker.cpp index 15a802d87..7a8d9f163 100644 --- a/src/rrr/rpc/circuit_breaker.cpp +++ b/src/rrr/rpc/circuit_breaker.cpp @@ -1,7 +1,8 @@ module; -#include +#include #include +#include #include export module rrr.circuit_breaker; @@ -69,6 +70,24 @@ struct CircuitBreakerConfig { } }; +// Free helper backing the OPEN→HALF_OPEN probe-readiness check in +// `CircuitBreaker::allow_request`. Returns true once the OPEN timeout +// has elapsed since the last failure. Authored as inline Rust DSL. +#if RUSTYCPP_RUST +fn circuit_should_probe(now_us: u64, last_failure_us: u64, timeout_ms: u32) -> bool { + let timeout_us: u64 = (timeout_ms as u64) * 1000; + now_us - last_failure_us >= timeout_us +} +#endif +/*RUSTYCPP:GEN-BEGIN id=circuit_breaker.1 version=1 rust_sha256=513b8ecf0d225fe0a92a55f713dc3bc14cd0c9025d89f408c9b1c22a5c02a5a7*/ +bool circuit_should_probe(uint64_t now_us, uint64_t last_failure_us, uint32_t timeout_ms); + +bool circuit_should_probe(uint64_t now_us, uint64_t last_failure_us, uint32_t timeout_ms) { + const uint64_t timeout_us = ((static_cast(timeout_ms))) * static_cast(1000); + return (rusty::detail::deref_if_pointer_like(now_us) - rusty::detail::deref_if_pointer_like(last_failure_us)) >= rusty::detail::deref_if_pointer_like(timeout_us); +} +/*RUSTYCPP:GEN-END id=circuit_breaker.1*/ + // @safe - Single-threaded circuit breaker state machine. All fields are // rusty::Cell for trivially-copyable interior mutability; no raw // pointers, syscalls, or operator-overload chains. @@ -108,11 +127,9 @@ class CircuitBreaker { return true; case CircuitState::OPEN: { - uint64_t now = current_time_us(); - uint64_t last = last_failure_time_.get(); - uint64_t timeout_us = static_cast(config_.timeout_ms) * 1000; - - if (now - last >= timeout_us) { + if (circuit_should_probe(current_time_us(), + last_failure_time_.get(), + config_.timeout_ms)) { state_.set(CircuitState::HALF_OPEN); probe_in_progress_.set(true); return true; From 1ab57e70d5d2c04e5cdaab5d5df1bfd727b06448 Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Thu, 28 May 2026 13:24:36 -0400 Subject: [PATCH 11/32] =?UTF-8?q?rrr/request=5Foptions:=20inline=20Rust=20?= =?UTF-8?q?DSL=20=E2=80=94=203=20predicate/arithmetic=20helpers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrates three small pure methods on `RequestOptions` to a single multi-fn `#if RUSTYCPP_RUST` block: - `can_retry(retry_count)` → `request_can_retry(idempotent, retry, max_retries)`: boolean predicate. - `is_total_timeout_exceeded(elapsed_ms)` → `request_total_timeout_exceeded(total_timeout_ms, elapsed_ms)`: bounds check. - `remaining_time_ms(elapsed_ms)` → `request_remaining_time_ms(total_timeout_ms, elapsed_ms)`: sentinel-returning arithmetic (0-value means "no limit", returns u64::MAX in that case). Each member method is now a one-line forwarder. First migration to use: - `u16` parameters — lowered to `uint16_t`. - `u64::MAX` — lowered to `std::numeric_limits::max()`. The `calculate_delay_ms` method is the obvious next candidate but uses `std::pow` + `thread_local std::mt19937` + `random_device` — the random side stays C++ and `std::pow(2.0, attempt)` needs a manual loop on the Rust side. Deferred. Verification: - `cmake --build build_clang21 --target rrr -j32` clean - `cmake --build build_clang21 --target borrow_check_rrr_borrow_request_options -j32` clean - `build_clang21/test_rpc_timeout_retry` — 36/36 PASSED Co-Authored-By: Claude Opus 4.7 --- docs/dev/rrr_rust_dsl_migration_plan.md | 8 ++++ src/rrr/rpc/request_options.cpp | 62 +++++++++++++++++++++---- 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index 1cbf5f5d4..efb76e1fe 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -194,6 +194,14 @@ keeping the C++ surface unchanged. the only candidate. rrr builds, borrow_check_rrr_borrow_circuit_breaker clean, `test_rpc_circuit_breaker` 21/21 pass. +- [x] `rpc/request_options.cpp::can_retry`, + `is_total_timeout_exceeded`, `remaining_time_ms` — three pure + predicates / sentinel-arithmetic methods migrated as one multi-fn + `#if RUSTYCPP_RUST` block. First migration with `u16` parameters + and `u64::MAX` (lowered to `std::numeric_limits::max()`). + Member methods are now one-line forwarders. rrr builds, + borrow_check_rrr_borrow_request_options clean, + `test_rpc_timeout_retry` 36/36 pass. ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/rpc/request_options.cpp b/src/rrr/rpc/request_options.cpp index 8284407b0..5efe4253e 100644 --- a/src/rrr/rpc/request_options.cpp +++ b/src/rrr/rpc/request_options.cpp @@ -1,6 +1,7 @@ module; -#include +#include +#include export module rrr.request_options; @@ -19,6 +20,53 @@ enum class TimeoutType : uint8_t { TOTAL_TIMEOUT }; +// Free helpers backing `RequestOptions::can_retry`, +// `is_total_timeout_exceeded`, and `remaining_time_ms`. Pure predicates +// / sentinel-arithmetic over struct fields; member methods are thin +// forwarders. Authored as inline Rust DSL. +#if RUSTYCPP_RUST +fn request_can_retry(idempotent: bool, current_retry_count: u16, max_retries: u16) -> bool { + idempotent && current_retry_count < max_retries +} + +fn request_total_timeout_exceeded(total_timeout_ms: u64, elapsed_ms: u64) -> bool { + total_timeout_ms > 0 && elapsed_ms >= total_timeout_ms +} + +fn request_remaining_time_ms(total_timeout_ms: u64, elapsed_ms: u64) -> u64 { + if total_timeout_ms == 0 { + return u64::MAX; + } + if elapsed_ms >= total_timeout_ms { + return 0; + } + total_timeout_ms - elapsed_ms +} +#endif +/*RUSTYCPP:GEN-BEGIN id=request_options.1 version=1 rust_sha256=a81c8f018bca3693c1162d3274454d5e840a8bc58ac674f32830ae7eca785871*/ +bool request_can_retry(bool idempotent, uint16_t current_retry_count, uint16_t max_retries); +bool request_total_timeout_exceeded(uint64_t total_timeout_ms, uint64_t elapsed_ms); +uint64_t request_remaining_time_ms(uint64_t total_timeout_ms, uint64_t elapsed_ms); + +bool request_can_retry(bool idempotent, uint16_t current_retry_count, uint16_t max_retries) { + return rusty::detail::deref_if_pointer_like(idempotent) && (rusty::detail::deref_if_pointer_like(current_retry_count) < rusty::detail::deref_if_pointer_like(max_retries)); +} + +bool request_total_timeout_exceeded(uint64_t total_timeout_ms, uint64_t elapsed_ms) { + return (rusty::detail::deref_if_pointer_like(total_timeout_ms) > 0) && (rusty::detail::deref_if_pointer_like(elapsed_ms) >= rusty::detail::deref_if_pointer_like(total_timeout_ms)); +} + +uint64_t request_remaining_time_ms(uint64_t total_timeout_ms, uint64_t elapsed_ms) { + if (rusty::detail::deref_if_pointer_like(total_timeout_ms) == static_cast(0)) { + return std::numeric_limits::max(); + } + if (rusty::detail::deref_if_pointer_like(elapsed_ms) >= rusty::detail::deref_if_pointer_like(total_timeout_ms)) { + return static_cast(0); + } + return rusty::detail::deref_if_pointer_like(total_timeout_ms) - rusty::detail::deref_if_pointer_like(elapsed_ms); +} +/*RUSTYCPP:GEN-END id=request_options.1*/ + struct RequestOptions { uint64_t timeout_ms = 1000; uint64_t total_timeout_ms = 0; @@ -78,7 +126,7 @@ struct RequestOptions { } bool can_retry(uint16_t current_retry_count) const { - return idempotent && current_retry_count < max_retries; + return request_can_retry(idempotent, current_retry_count, max_retries); } uint64_t calculate_delay_ms(uint16_t attempt) const { @@ -104,17 +152,11 @@ struct RequestOptions { } bool is_total_timeout_exceeded(uint64_t elapsed_ms) const { - return total_timeout_ms > 0 && elapsed_ms >= total_timeout_ms; + return request_total_timeout_exceeded(total_timeout_ms, elapsed_ms); } uint64_t remaining_time_ms(uint64_t elapsed_ms) const { - if (total_timeout_ms == 0) { - return UINT64_MAX; - } - if (elapsed_ms >= total_timeout_ms) { - return 0; - } - return total_timeout_ms - elapsed_ms; + return request_remaining_time_ms(total_timeout_ms, elapsed_ms); } }; From 13050fba1f5983bce8ee18c8c71d14bca5586bed Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Thu, 28 May 2026 13:29:09 -0400 Subject: [PATCH 12/32] =?UTF-8?q?rrr/connection=5Fstate:=20inline=20Rust?= =?UTF-8?q?=20DSL=20=E2=80=94=204=20state=20classifiers=20+=20transition?= =?UTF-8?q?=20table?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrates four pure-logic methods on `ConnectionStateMachine` to a single multi-fn `#if RUSTYCPP_RUST` block: - `is_valid_transition(from, to)` — the 6-state transition table (NEW/CONNECTING/CONNECTED/DISCONNECTING/DISCONNECTED/FAILED). Authored as an if-chain (each `from` state mapped explicitly), not a `switch` or `match` — the latter trips the heavyweight runtime lowering documented in the plan doc's Quirks section. - `is_terminal(s)` — true when s ∈ {DISCONNECTED, FAILED}. - `can_connect(s)` — true when s ∈ {NEW, DISCONNECTED, FAILED}. - `is_usable(s)` — true when s ∈ {CONNECTING, CONNECTED}. All four Rust helpers take the raw `i32` discriminant of `ConnectionState`; the C++ member methods cast at the boundary so the public typed-enum surface (`ConnectionState`) is unchanged. Verification: - `cmake --build build_clang21 --target rrr -j32` clean - `cmake --build build_clang21 --target borrow_check_rrr_borrow_connection_state -j32` clean - `build_clang21/test_rpc_connection_state` — 30/30 PASSED - `build_clang21/test_rpc_state_integration` — 16/16 PASSED Co-Authored-By: Claude Opus 4.7 --- docs/dev/rrr_rust_dsl_migration_plan.md | 11 ++ src/rrr/rpc/connection_state.cpp | 128 +++++++++++++++++------- 2 files changed, 105 insertions(+), 34 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index efb76e1fe..954e55266 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -202,6 +202,17 @@ keeping the C++ surface unchanged. Member methods are now one-line forwarders. rrr builds, borrow_check_rrr_borrow_request_options clean, `test_rpc_timeout_retry` 36/36 pass. +- [x] `rpc/connection_state.cpp::is_valid_transition` + + `is_terminal` + `can_connect` + `is_usable` — four pure state + classifiers + the central transition table migrated as one + multi-fn `#if RUSTYCPP_RUST` block. The Rust helpers take the + raw `i32` discriminant of `ConnectionState` (NEW=0..FAILED=5); + C++ member methods cast at the boundary. First migration that + lowers a multi-arm `switch` — done as an if-chain (per the match + quirk in the Quirks section). rrr builds, + borrow_check_rrr_borrow_connection_state clean, + `test_rpc_connection_state` 30/30 + `test_rpc_state_integration` + 16/16 pass. ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/rpc/connection_state.cpp b/src/rrr/rpc/connection_state.cpp index 60dd5f540..ecd9c94e2 100644 --- a/src/rrr/rpc/connection_state.cpp +++ b/src/rrr/rpc/connection_state.cpp @@ -1,8 +1,10 @@ module; +#include #include #include #include +#include export module rrr.connection_state; @@ -31,6 +33,93 @@ inline const char* connection_state_to_string(ConnectionState state) { } } +// Free helpers backing the pure state-classifier predicates on +// `ConnectionStateMachine` (and the central transition table). Each +// helper takes the raw integer discriminant of `ConnectionState` +// (NEW=0..FAILED=5); the C++ member methods cast at the boundary. +// Authored as inline Rust DSL. +#if RUSTYCPP_RUST +fn connection_is_valid_transition(from: i32, to: i32) -> bool { + if from == 0 { + // NEW → CONNECTING + return to == 1; + } + if from == 1 { + // CONNECTING → CONNECTED | FAILED | DISCONNECTED + return to == 2 || to == 5 || to == 4; + } + if from == 2 { + // CONNECTED → DISCONNECTING | FAILED + return to == 3 || to == 5; + } + if from == 3 { + // DISCONNECTING → DISCONNECTED | FAILED + return to == 4 || to == 5; + } + if from == 4 { + // DISCONNECTED → CONNECTING + return to == 1; + } + if from == 5 { + // FAILED → CONNECTING + return to == 1; + } + false +} + +fn connection_is_terminal(s: i32) -> bool { + s == 4 || s == 5 +} + +fn connection_can_connect(s: i32) -> bool { + s == 0 || s == 4 || s == 5 +} + +fn connection_is_usable(s: i32) -> bool { + s == 1 || s == 2 +} +#endif +/*RUSTYCPP:GEN-BEGIN id=connection_state.1 version=1 rust_sha256=efa3b250b9497ffd63a43664a7493db5719f3bf70c23bf0531de70907c04fe51*/ +bool connection_is_valid_transition(int32_t from, int32_t to); +bool connection_is_terminal(int32_t s); +bool connection_can_connect(int32_t s); +bool connection_is_usable(int32_t s); + +bool connection_is_valid_transition(int32_t from, int32_t to) { + if (rusty::detail::deref_if_pointer_like(from) == static_cast(0)) { + return rusty::detail::deref_if_pointer_like(to) == static_cast(1); + } + if (rusty::detail::deref_if_pointer_like(from) == static_cast(1)) { + return ((rusty::detail::deref_if_pointer_like(to) == static_cast(2)) || (rusty::detail::deref_if_pointer_like(to) == static_cast(5))) || (rusty::detail::deref_if_pointer_like(to) == static_cast(4)); + } + if (rusty::detail::deref_if_pointer_like(from) == static_cast(2)) { + return (rusty::detail::deref_if_pointer_like(to) == static_cast(3)) || (rusty::detail::deref_if_pointer_like(to) == static_cast(5)); + } + if (rusty::detail::deref_if_pointer_like(from) == static_cast(3)) { + return (rusty::detail::deref_if_pointer_like(to) == static_cast(4)) || (rusty::detail::deref_if_pointer_like(to) == static_cast(5)); + } + if (rusty::detail::deref_if_pointer_like(from) == static_cast(4)) { + return rusty::detail::deref_if_pointer_like(to) == static_cast(1); + } + if (rusty::detail::deref_if_pointer_like(from) == static_cast(5)) { + return rusty::detail::deref_if_pointer_like(to) == static_cast(1); + } + return false; +} + +bool connection_is_terminal(int32_t s) { + return (rusty::detail::deref_if_pointer_like(s) == static_cast(4)) || (rusty::detail::deref_if_pointer_like(s) == static_cast(5)); +} + +bool connection_can_connect(int32_t s) { + return ((rusty::detail::deref_if_pointer_like(s) == static_cast(0)) || (rusty::detail::deref_if_pointer_like(s) == static_cast(4))) || (rusty::detail::deref_if_pointer_like(s) == static_cast(5)); +} + +bool connection_is_usable(int32_t s) { + return (rusty::detail::deref_if_pointer_like(s) == static_cast(1)) || (rusty::detail::deref_if_pointer_like(s) == static_cast(2)); +} +/*RUSTYCPP:GEN-END id=connection_state.1*/ + // @safe - Pure state machine: rusty::Cell + rusty::Function // callback. No raw pointers, syscalls, or operator-overload chains. class ConnectionStateMachine { @@ -107,50 +196,21 @@ class ConnectionStateMachine { } bool is_terminal() const { - ConnectionState s = state_.get(); - return s == ConnectionState::DISCONNECTED || s == ConnectionState::FAILED; + return connection_is_terminal(static_cast(state_.get())); } bool can_connect() const { - ConnectionState s = state_.get(); - return s == ConnectionState::NEW || - s == ConnectionState::DISCONNECTED || - s == ConnectionState::FAILED; + return connection_can_connect(static_cast(state_.get())); } bool is_usable() const { - ConnectionState s = state_.get(); - return s == ConnectionState::CONNECTING || s == ConnectionState::CONNECTED; + return connection_is_usable(static_cast(state_.get())); } private: static bool is_valid_transition(ConnectionState from, ConnectionState to) { - switch (from) { - case ConnectionState::NEW: - return to == ConnectionState::CONNECTING; - - case ConnectionState::CONNECTING: - return to == ConnectionState::CONNECTED || - to == ConnectionState::FAILED || - to == ConnectionState::DISCONNECTED; - - case ConnectionState::CONNECTED: - return to == ConnectionState::DISCONNECTING || - to == ConnectionState::FAILED; - - case ConnectionState::DISCONNECTING: - return to == ConnectionState::DISCONNECTED || - to == ConnectionState::FAILED; - - case ConnectionState::DISCONNECTED: - return to == ConnectionState::CONNECTING; - - case ConnectionState::FAILED: - return to == ConnectionState::CONNECTING; - - default: - return false; - } + return connection_is_valid_transition( + static_cast(from), static_cast(to)); } }; From 9bb81e0f56638efad55d2981db363e95534adcf2 Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Thu, 28 May 2026 13:33:26 -0400 Subject: [PATCH 13/32] =?UTF-8?q?rrr/heartbeat:=20inline=20Rust=20DSL=20?= =?UTF-8?q?=E2=80=94=203=20timing-arithmetic=20helpers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrates the timing-elapsed checks inside `HeartbeatManager:: should_send_heartbeat`, `check_timeout`, and `time_until_next_heartbeat_ms` to a single multi-fn `#if RUSTYCPP_RUST` block: - `heartbeat_interval_elapsed(now_us, last_send_us, interval_ms)` - `heartbeat_timeout_elapsed(now_us, last_send_us, timeout_ms)` - `heartbeat_time_until_next_ms(now_us, last_send_us, interval_ms)` All three are pure `u64`/`u32` math (convert `_ms` to `_us`, compare against a `now - last` window). The C++ member methods still own the Cell reads + enabled/timed_out/pending_pong guards; only the deterministic arithmetic moves to Rust DSL. Verification: - `cmake --build build_clang21 --target rrr -j32` clean - `cmake --build build_clang21 --target borrow_check_rrr_borrow_heartbeat -j32` clean - `build_clang21/test_rpc_heartbeat` — 20/20 PASSED Co-Authored-By: Claude Opus 4.7 --- docs/dev/rrr_rust_dsl_migration_plan.md | 9 +++ src/rrr/rpc/heartbeat.cpp | 86 +++++++++++++++++++------ 2 files changed, 74 insertions(+), 21 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index 954e55266..36010a5e8 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -213,6 +213,15 @@ keeping the C++ surface unchanged. borrow_check_rrr_borrow_connection_state clean, `test_rpc_connection_state` 30/30 + `test_rpc_state_integration` 16/16 pass. +- [x] `rpc/heartbeat.cpp::should_send_heartbeat`, + `check_timeout`, `time_until_next_heartbeat_ms` — three + timing-arithmetic helpers (`heartbeat_interval_elapsed`, + `heartbeat_timeout_elapsed`, `heartbeat_time_until_next_ms`) + in one multi-fn block. Each converts `_ms → _us` and compares + against a `now - last` elapsed window. C++ wrappers still own + the Cell reads + enabled/timed_out guards. rrr builds, + borrow_check_rrr_borrow_heartbeat clean, + `test_rpc_heartbeat` 20/20 pass. ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/rpc/heartbeat.cpp b/src/rrr/rpc/heartbeat.cpp index 77d2da3ad..d9d62fd31 100644 --- a/src/rrr/rpc/heartbeat.cpp +++ b/src/rrr/rpc/heartbeat.cpp @@ -1,9 +1,10 @@ module; -#include -#include // std::abort referenced by rusty/function.hpp +#include +#include // abort() referenced by rusty/function.hpp #include #include +#include #include export module rrr.heartbeat; @@ -56,6 +57,56 @@ struct HeartbeatConfig { } }; +// Free helpers backing the timing-arithmetic portions of +// `HeartbeatManager::should_send_heartbeat`, `check_timeout`, and +// `time_until_next_heartbeat_ms`. All three convert `_ms` to `_us` +// and compare against a "now - last" elapsed window. Authored as +// inline Rust DSL. +#if RUSTYCPP_RUST +fn heartbeat_interval_elapsed(now_us: u64, last_send_us: u64, interval_ms: u32) -> bool { + let interval_us: u64 = (interval_ms as u64) * 1000; + now_us - last_send_us >= interval_us +} + +fn heartbeat_timeout_elapsed(now_us: u64, last_send_us: u64, timeout_ms: u32) -> bool { + let timeout_us: u64 = (timeout_ms as u64) * 1000; + now_us - last_send_us >= timeout_us +} + +fn heartbeat_time_until_next_ms(now_us: u64, last_send_us: u64, interval_ms: u32) -> u32 { + let interval_us: u64 = (interval_ms as u64) * 1000; + let elapsed_us: u64 = now_us - last_send_us; + if elapsed_us >= interval_us { + return 0; + } + ((interval_us - elapsed_us) / 1000) as u32 +} +#endif +/*RUSTYCPP:GEN-BEGIN id=heartbeat.1 version=1 rust_sha256=ad0dc63f92a1763ba1d8538d2f3dcce52c56109bf1c6a96eec09b5ea43d3e67d*/ +bool heartbeat_interval_elapsed(uint64_t now_us, uint64_t last_send_us, uint32_t interval_ms); +bool heartbeat_timeout_elapsed(uint64_t now_us, uint64_t last_send_us, uint32_t timeout_ms); +uint32_t heartbeat_time_until_next_ms(uint64_t now_us, uint64_t last_send_us, uint32_t interval_ms); + +bool heartbeat_interval_elapsed(uint64_t now_us, uint64_t last_send_us, uint32_t interval_ms) { + const uint64_t interval_us = ((static_cast(interval_ms))) * static_cast(1000); + return (rusty::detail::deref_if_pointer_like(now_us) - rusty::detail::deref_if_pointer_like(last_send_us)) >= rusty::detail::deref_if_pointer_like(interval_us); +} + +bool heartbeat_timeout_elapsed(uint64_t now_us, uint64_t last_send_us, uint32_t timeout_ms) { + const uint64_t timeout_us = ((static_cast(timeout_ms))) * static_cast(1000); + return (rusty::detail::deref_if_pointer_like(now_us) - rusty::detail::deref_if_pointer_like(last_send_us)) >= rusty::detail::deref_if_pointer_like(timeout_us); +} + +uint32_t heartbeat_time_until_next_ms(uint64_t now_us, uint64_t last_send_us, uint32_t interval_ms) { + const uint64_t interval_us = ((static_cast(interval_ms))) * static_cast(1000); + const uint64_t elapsed_us = rusty::detail::deref_if_pointer_like(now_us) - rusty::detail::deref_if_pointer_like(last_send_us); + if (rusty::detail::deref_if_pointer_like(elapsed_us) >= rusty::detail::deref_if_pointer_like(interval_us)) { + return static_cast(0); + } + return static_cast((((rusty::detail::deref_if_pointer_like(interval_us) - rusty::detail::deref_if_pointer_like(elapsed_us))) / 1000)); +} +/*RUSTYCPP:GEN-END id=heartbeat.1*/ + // @safe - Heartbeat tracker. Fields are rusty::Cell for trivially- // copyable interior mutability + rusty::Function for the timeout // callback. No raw pointers, syscalls, or operator-overload chains. @@ -95,11 +146,10 @@ class HeartbeatManager { return false; } - uint64_t now = heartbeat_time_us(); - uint64_t last = last_send_time_.get(); - uint64_t interval_us = static_cast(config_.interval_ms) * 1000; - - return (now - last >= interval_us); + return heartbeat_interval_elapsed( + heartbeat_time_us(), + last_send_time_.get(), + config_.interval_ms); } void on_heartbeat_sent() { @@ -127,11 +177,10 @@ class HeartbeatManager { return false; } - uint64_t now = heartbeat_time_us(); - uint64_t sent = last_send_time_.get(); - uint64_t timeout_us = static_cast(config_.timeout_ms) * 1000; - - if (now - sent >= timeout_us) { + if (heartbeat_timeout_elapsed( + heartbeat_time_us(), + last_send_time_.get(), + config_.timeout_ms)) { pending_pong_.set(false); uint32_t count = missed_count_.get() + 1; missed_count_.set(count); @@ -154,15 +203,10 @@ class HeartbeatManager { return config_.interval_ms; } - uint64_t now = heartbeat_time_us(); - uint64_t last = last_send_time_.get(); - uint64_t interval_us = static_cast(config_.interval_ms) * 1000; - - if (now - last >= interval_us) { - return 0; - } - - return static_cast((interval_us - (now - last)) / 1000); + return heartbeat_time_until_next_ms( + heartbeat_time_us(), + last_send_time_.get(), + config_.interval_ms); } bool is_timed_out() const { From 02f3b6920780976c81d88ee007244849d45991b0 Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Thu, 28 May 2026 13:37:17 -0400 Subject: [PATCH 14/32] =?UTF-8?q?rrr/connection=5Fmetrics:=20inline=20Rust?= =?UTF-8?q?=20DSL=20=E2=80=94=204=20derived-stat=20helpers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrates the derived-statistic methods on `ConnectionMetrics` to a single multi-fn `#if RUSTYCPP_RUST` block: - `min_latency_us()` → `metrics_min_latency_us(stored)`: maps u64::MAX sentinel to 0. - `success_rate_percent()` → `metrics_success_rate_percent(completed, total)`: 100 when total=0, else `(completed*100)/total`. - `avg_latency_us()` → `metrics_avg_latency_us(total_us, completed)`: 0 when completed=0, else `total_us/completed`. - `uptime_ms(now)` → `metrics_uptime_ms(connect_time, now)`: 0 when connect_time=0 or now --- docs/dev/rrr_rust_dsl_migration_plan.md | 8 +++ src/rrr/rpc/connection_metrics.cpp | 96 +++++++++++++++++++++---- 2 files changed, 90 insertions(+), 14 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index 36010a5e8..ccc703bd1 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -222,6 +222,14 @@ keeping the C++ surface unchanged. the Cell reads + enabled/timed_out guards. rrr builds, borrow_check_rrr_borrow_heartbeat clean, `test_rpc_heartbeat` 20/20 pass. +- [x] `rpc/connection_metrics.cpp::min_latency_us`, + `success_rate_percent`, `avg_latency_us`, `uptime_ms` — four + pure `u64` sentinel/division helpers in one multi-fn block + (`metrics_min_latency_us`, `metrics_success_rate_percent`, + `metrics_avg_latency_us`, `metrics_uptime_ms`). C++ member + methods read Cells and forward. rrr builds, + borrow_check_rrr_borrow_connection_metrics clean, + `test_rpc_metrics` 25/25 pass. ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/rpc/connection_metrics.cpp b/src/rrr/rpc/connection_metrics.cpp index 245bc1ff2..ae2f4a631 100644 --- a/src/rrr/rpc/connection_metrics.cpp +++ b/src/rrr/rpc/connection_metrics.cpp @@ -1,7 +1,8 @@ module; -#include +#include #include +#include export module rrr.connection_metrics; @@ -11,6 +12,80 @@ import std; // getters/setters. No raw pointers, syscalls, or operator-overload chains. export namespace rrr { +// Free helpers backing the derived-statistic methods on +// `ConnectionMetrics`. All four are pure `u64` math with sentinel +// edge cases. C++ member methods read the Cells and forward. +// Authored as inline Rust DSL. +#if RUSTYCPP_RUST +fn metrics_min_latency_us(stored: u64) -> u64 { + if stored == u64::MAX { + return 0; + } + stored +} + +fn metrics_success_rate_percent(completed: u64, total: u64) -> u64 { + if total == 0 { + return 100; + } + (completed * 100) / total +} + +fn metrics_avg_latency_us(total_latency_us: u64, completed: u64) -> u64 { + if completed == 0 { + return 0; + } + total_latency_us / completed +} + +fn metrics_uptime_ms(connect_time_ms: u64, current_time_ms: u64) -> u64 { + if connect_time_ms == 0 { + return 0; + } + if current_time_ms < connect_time_ms { + return 0; + } + current_time_ms - connect_time_ms +} +#endif +/*RUSTYCPP:GEN-BEGIN id=connection_metrics.1 version=1 rust_sha256=5914ef78b48145b14484486dfca96692f4814f202a02c689277974e6dd11c07e*/ +uint64_t metrics_min_latency_us(uint64_t stored); +uint64_t metrics_success_rate_percent(uint64_t completed, uint64_t total); +uint64_t metrics_avg_latency_us(uint64_t total_latency_us, uint64_t completed); +uint64_t metrics_uptime_ms(uint64_t connect_time_ms, uint64_t current_time_ms); + +uint64_t metrics_min_latency_us(uint64_t stored) { + if (rusty::detail::deref_if_pointer_like(stored) == rusty::detail::deref_if_pointer_like(std::numeric_limits::max())) { + return static_cast(0); + } + return std::move(stored); +} + +uint64_t metrics_success_rate_percent(uint64_t completed, uint64_t total) { + if (rusty::detail::deref_if_pointer_like(total) == static_cast(0)) { + return static_cast(100); + } + return ((rusty::detail::deref_if_pointer_like(completed) * static_cast(100))) / rusty::detail::deref_if_pointer_like(total); +} + +uint64_t metrics_avg_latency_us(uint64_t total_latency_us, uint64_t completed) { + if (rusty::detail::deref_if_pointer_like(completed) == static_cast(0)) { + return static_cast(0); + } + return rusty::detail::deref_if_pointer_like(total_latency_us) / rusty::detail::deref_if_pointer_like(completed); +} + +uint64_t metrics_uptime_ms(uint64_t connect_time_ms, uint64_t current_time_ms) { + if (rusty::detail::deref_if_pointer_like(connect_time_ms) == static_cast(0)) { + return static_cast(0); + } + if (rusty::detail::deref_if_pointer_like(current_time_ms) < rusty::detail::deref_if_pointer_like(connect_time_ms)) { + return static_cast(0); + } + return rusty::detail::deref_if_pointer_like(current_time_ms) - rusty::detail::deref_if_pointer_like(connect_time_ms); +} +/*RUSTYCPP:GEN-END id=connection_metrics.1*/ + class ConnectionMetrics { public: ConnectionMetrics() = default; @@ -34,29 +109,22 @@ class ConnectionMetrics { uint64_t connect_time_ms() const { return connect_time_ms_.get(); } uint64_t min_latency_us() const { - auto min = min_latency_us_.get(); - return (min == std::numeric_limits::max()) ? 0 : min; + return metrics_min_latency_us(min_latency_us_.get()); } uint64_t max_latency_us() const { return max_latency_us_.get(); } uint64_t success_rate_percent() const { - auto completed = requests_completed_.get(); - auto total = requests_sent_.get(); - if (total == 0) return 100; - return (completed * 100) / total; + return metrics_success_rate_percent( + requests_completed_.get(), requests_sent_.get()); } uint64_t avg_latency_us() const { - auto completed = requests_completed_.get(); - if (completed == 0) return 0; - return total_latency_us_.get() / completed; + return metrics_avg_latency_us( + total_latency_us_.get(), requests_completed_.get()); } uint64_t uptime_ms(uint64_t current_time_ms) const { - auto connect_time = connect_time_ms_.get(); - if (connect_time == 0) return 0; - if (current_time_ms < connect_time) return 0; - return current_time_ms - connect_time; + return metrics_uptime_ms(connect_time_ms_.get(), current_time_ms); } void record_request_sent() const { From 1b9dd93fa92390484ac890a9575fcecfa16f1ae9 Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Thu, 28 May 2026 13:45:51 -0400 Subject: [PATCH 15/32] =?UTF-8?q?rrr/internal=5Fprotocol:=20inline=20Rust?= =?UTF-8?q?=20DSL=20=E2=80=94=203=20bit-twiddling=20helpers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert response_has_extended_header / response_payload_size / encode_response_size to one multi-fn `#if RUSTYCPP_RUST` block. High bit of the i32-encoded size marks "extended header" (response carries after ); low 31 bits hold payload size. C++ wrappers are now one-line forwarders; lost the `constexpr` qualifier (now `inline`) — verified no callers use these in constexpr contexts via grep across the rrr tree. borrow_check_rrr_borrow_internal_protocol clean, test_rpc_frame_codec 25/25 pass. Co-Authored-By: Claude Opus 4.7 --- docs/dev/rrr_rust_dsl_migration_plan.md | 12 ++++++ src/rrr/rpc/internal_protocol.cpp | 57 +++++++++++++++++++++---- 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index ccc703bd1..81d05a3ac 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -230,6 +230,18 @@ keeping the C++ surface unchanged. methods read Cells and forward. rrr builds, borrow_check_rrr_borrow_connection_metrics clean, `test_rpc_metrics` 25/25 pass. +- [x] `rpc/internal_protocol.cpp::response_has_extended_header`, + `response_payload_size`, `encode_response_size` — three pure + bit-twiddling helpers in one multi-fn block + (`internal_protocol_response_has_extended_header`, + `internal_protocol_response_payload_size`, + `internal_protocol_encode_response_size`). High bit of the + i32-encoded size marks "extended header"; low 31 bits hold + payload size. Lost the `constexpr` qualifier on the public + wrappers (now `inline`) — verified no callers use these in + constexpr contexts. rrr builds, + borrow_check_rrr_borrow_internal_protocol clean, + `test_rpc_frame_codec` 25/25 pass. ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/rpc/internal_protocol.cpp b/src/rrr/rpc/internal_protocol.cpp index bc70a4d9e..1f1d6c20f 100644 --- a/src/rrr/rpc/internal_protocol.cpp +++ b/src/rrr/rpc/internal_protocol.cpp @@ -1,12 +1,13 @@ module; -#include +#include +#include export module rrr.internal_protocol; import std; -// @safe - Wire-protocol constants + pure constexpr bit-twiddling helpers. +// @safe - Wire-protocol constants + pure bit-twiddling helpers. // No raw pointers, syscalls, or operator-overload chains. export namespace rrr { @@ -15,17 +16,55 @@ constexpr int32_t kInternalHeartbeatRpcId = std::numeric_limits::min(); constexpr uint32_t kResponseHeaderExtFlag = 0x80000000u; constexpr uint32_t kResponseSizeMask = 0x7fffffffu; -inline constexpr bool response_has_extended_header(int32_t encoded_size) { - return (static_cast(encoded_size) & kResponseHeaderExtFlag) != 0; +// Free helpers backing the three response-header bit-twiddling +// inlines. The high bit of the encoded i32 marks "extended header" +// (response carries `` after ``); +// the low 31 bits hold the payload size. Authored as inline Rust DSL. +#if RUSTYCPP_RUST +fn internal_protocol_response_has_extended_header(encoded_size: i32) -> bool { + ((encoded_size as u32) & 0x80000000) != 0 } -inline constexpr int32_t response_payload_size(int32_t encoded_size) { - return static_cast(static_cast(encoded_size) & kResponseSizeMask); +fn internal_protocol_response_payload_size(encoded_size: i32) -> i32 { + ((encoded_size as u32) & 0x7fffffff) as i32 } -inline constexpr int32_t encode_response_size(int32_t payload_size, bool extended_header) { - const uint32_t base = static_cast(payload_size) & kResponseSizeMask; - return static_cast(extended_header ? (base | kResponseHeaderExtFlag) : base); +fn internal_protocol_encode_response_size(payload_size: i32, extended_header: bool) -> i32 { + let base: u32 = (payload_size as u32) & 0x7fffffff; + let out: u32 = if extended_header { base | 0x80000000 } else { base }; + out as i32 +} +#endif +/*RUSTYCPP:GEN-BEGIN id=internal_protocol.1 version=1 rust_sha256=c89038524f01c6432929da2c60b1a9de977915d779ae3a676b173e910a67f128*/ +bool internal_protocol_response_has_extended_header(int32_t encoded_size); +int32_t internal_protocol_response_payload_size(int32_t encoded_size); +int32_t internal_protocol_encode_response_size(int32_t payload_size, bool extended_header); + +bool internal_protocol_response_has_extended_header(int32_t encoded_size) { + return ((((static_cast(encoded_size))) & static_cast(2147483648))) != static_cast(0); +} + +int32_t internal_protocol_response_payload_size(int32_t encoded_size) { + return static_cast((((static_cast(encoded_size))) & 2147483647)); +} + +int32_t internal_protocol_encode_response_size(int32_t payload_size, bool extended_header) { + const uint32_t base = ((static_cast(payload_size))) & static_cast(2147483647); + const uint32_t out = (extended_header ? rusty::detail::deref_if_pointer_like(base) | static_cast(2147483648) : base); + return static_cast(out); +} +/*RUSTYCPP:GEN-END id=internal_protocol.1*/ + +inline bool response_has_extended_header(int32_t encoded_size) { + return internal_protocol_response_has_extended_header(encoded_size); +} + +inline int32_t response_payload_size(int32_t encoded_size) { + return internal_protocol_response_payload_size(encoded_size); +} + +inline int32_t encode_response_size(int32_t payload_size, bool extended_header) { + return internal_protocol_encode_response_size(payload_size, extended_header); } } // export namespace rrr From a33063475f8f08c49e56a4c76467acd77437c455 Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Thu, 28 May 2026 13:49:18 -0400 Subject: [PATCH 16/32] =?UTF-8?q?rrr/idempotency:=20inline=20Rust=20DSL=20?= =?UTF-8?q?=E2=80=94=203=20predicate/statistic=20helpers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert IdempotencyKey::is_valid, CachedResponse::is_expired, and IdempotencyCache::hit_rate to one multi-fn `#if RUSTYCPP_RUST` block. First migration with `f64` return — `(hits as f64) / (total as f64)` lowers cleanly. C++ member methods read struct fields / Cells and forward. borrow_check_rrr_borrow_idempotency clean, test_idempotency 32/32 pass. Co-Authored-By: Claude Opus 4.7 --- docs/dev/rrr_rust_dsl_migration_plan.md | 9 ++++ src/rrr/rpc/idempotency.cpp | 65 +++++++++++++++++++++---- 2 files changed, 64 insertions(+), 10 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index 81d05a3ac..30bfbccc5 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -242,6 +242,15 @@ keeping the C++ surface unchanged. constexpr contexts. rrr builds, borrow_check_rrr_borrow_internal_protocol clean, `test_rpc_frame_codec` 25/25 pass. +- [x] `rpc/idempotency.cpp::IdempotencyKey::is_valid`, + `CachedResponse::is_expired`, `IdempotencyCache::hit_rate` — + three pure predicates / statistics in one multi-fn block + (`idempotency_key_is_valid`, `idempotency_response_is_expired`, + `idempotency_cache_hit_rate`). First migration with `f64` + return: `(hits as f64) / (total as f64)`. C++ member methods + read struct fields / Cells and forward. rrr builds, + borrow_check_rrr_borrow_idempotency clean, + `test_idempotency` 32/32 pass. ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/rpc/idempotency.cpp b/src/rrr/rpc/idempotency.cpp index 3a89f227f..cf78731ef 100644 --- a/src/rrr/rpc/idempotency.cpp +++ b/src/rrr/rpc/idempotency.cpp @@ -1,7 +1,7 @@ module; -#include -#include +#include +#include #include #include @@ -20,6 +20,56 @@ import rrr.threading; export namespace rrr { +// Free helpers backing three pure predicates / statistics on the +// idempotency types: `IdempotencyKey::is_valid`, +// `CachedResponse::is_expired`, and `IdempotencyCache::hit_rate`. All +// three take primitive `u64` arguments; C++ wrappers read struct +// fields / Cells and forward. Authored as inline Rust DSL. +#if RUSTYCPP_RUST +fn idempotency_key_is_valid(client_id: u64, sequence: u64) -> bool { + client_id != 0 || sequence != 0 +} + +fn idempotency_response_is_expired(current_time_ms: u64, timestamp_ms: u64, ttl_ms: u64) -> bool { + if ttl_ms == 0 { + return false; + } + current_time_ms > timestamp_ms + ttl_ms +} + +fn idempotency_cache_hit_rate(hits: u64, misses: u64) -> f64 { + let total: u64 = hits + misses; + if total == 0 { + return 0.0; + } + (hits as f64) / (total as f64) +} +#endif +/*RUSTYCPP:GEN-BEGIN id=idempotency.1 version=1 rust_sha256=7c17b88f14f40486c4624285c442236be8075da9098d7fcc2f918aae7e303e97*/ +bool idempotency_key_is_valid(uint64_t client_id, uint64_t sequence); +bool idempotency_response_is_expired(uint64_t current_time_ms, uint64_t timestamp_ms, uint64_t ttl_ms); +double idempotency_cache_hit_rate(uint64_t hits, uint64_t misses); + +bool idempotency_key_is_valid(uint64_t client_id, uint64_t sequence) { + return (rusty::detail::deref_if_pointer_like(client_id) != static_cast(0)) || (rusty::detail::deref_if_pointer_like(sequence) != static_cast(0)); +} + +bool idempotency_response_is_expired(uint64_t current_time_ms, uint64_t timestamp_ms, uint64_t ttl_ms) { + if (rusty::detail::deref_if_pointer_like(ttl_ms) == static_cast(0)) { + return false; + } + return rusty::detail::deref_if_pointer_like(current_time_ms) > (rusty::detail::deref_if_pointer_like(timestamp_ms) + rusty::detail::deref_if_pointer_like(ttl_ms)); +} + +double idempotency_cache_hit_rate(uint64_t hits, uint64_t misses) { + const uint64_t total = rusty::detail::deref_if_pointer_like(hits) + rusty::detail::deref_if_pointer_like(misses); + if (rusty::detail::deref_if_pointer_like(total) == static_cast(0)) { + return 0.0; + } + return ((static_cast(hits))) / ((static_cast(total))); +} +/*RUSTYCPP:GEN-END id=idempotency.1*/ + // =========================================================================== // IdempotencyKey @@ -54,7 +104,7 @@ struct IdempotencyKey { // @safe - Check if key is valid (non-zero) bool is_valid() const { - return client_id != 0 || sequence != 0; + return idempotency_key_is_valid(client_id, sequence); } // @safe - Create an empty/invalid key @@ -164,8 +214,7 @@ struct CachedResponse { // @safe - Check if entry has expired bool is_expired(uint64_t current_time_ms, uint64_t ttl_ms) const { - if (ttl_ms == 0) return false; // No expiration - return current_time_ms > timestamp_ms + ttl_ms; + return idempotency_response_is_expired(current_time_ms, timestamp_ms, ttl_ms); } // @unsafe - Copy response data from Marshal @@ -458,11 +507,7 @@ class IdempotencyCache { // @safe - Get hit rate (0.0 to 1.0) double hit_rate() const { - uint64_t h = hits_.get(); - uint64_t m = misses_.get(); - uint64_t total = h + m; - if (total == 0) return 0.0; - return static_cast(h) / static_cast(total); + return idempotency_cache_hit_rate(hits_.get(), misses_.get()); } // @safe - Reset statistics From 703ed13f39f49dcfee4ab42f781cbd4a0a967c1c Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Thu, 28 May 2026 13:53:05 -0400 Subject: [PATCH 17/32] =?UTF-8?q?rrr/completion=5Ftracker:=20inline=20Rust?= =?UTF-8?q?=20DSL=20=E2=80=94=203=20predicate/statistic=20helpers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert CompletedEntry::is_expired, CompletionTracker::hit_rate, and CompletionQueryResult::is_completed to one multi-fn `#if RUSTYCPP_RUST` block. First migration with `u8` parameter — the is_completed helper takes the `u8` discriminant of CompletionStatus (NOT_FOUND=0.. EXPIRED=3); C++ casts at the boundary. borrow_check_rrr_borrow_completion_tracker clean, test_completion_tracker 27/27 pass. Co-Authored-By: Claude Opus 4.7 --- docs/dev/rrr_rust_dsl_migration_plan.md | 11 +++++ src/rrr/rpc/completion_tracker.cpp | 64 +++++++++++++++++++++---- 2 files changed, 66 insertions(+), 9 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index 30bfbccc5..2eae61687 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -251,6 +251,17 @@ keeping the C++ surface unchanged. read struct fields / Cells and forward. rrr builds, borrow_check_rrr_borrow_idempotency clean, `test_idempotency` 32/32 pass. +- [x] `rpc/completion_tracker.cpp::CompletedEntry::is_expired`, + `CompletionTracker::hit_rate`, + `CompletionQueryResult::is_completed` — three pure predicates / + statistics in one multi-fn block (`completion_entry_is_expired`, + `completion_tracker_hit_rate`, + `completion_query_result_is_completed`). First migration with + `u8` parameter: the last helper takes the `u8` discriminant of + `CompletionStatus` (NOT_FOUND=0..EXPIRED=3); C++ casts at the + boundary. rrr builds, + borrow_check_rrr_borrow_completion_tracker clean, + `test_completion_tracker` 27/27 pass. ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/rpc/completion_tracker.cpp b/src/rrr/rpc/completion_tracker.cpp index dce9d2ba4..a4606f3bc 100644 --- a/src/rrr/rpc/completion_tracker.cpp +++ b/src/rrr/rpc/completion_tracker.cpp @@ -1,7 +1,7 @@ module; -#include -#include +#include +#include #include #include @@ -14,6 +14,56 @@ import rrr.idempotency; export namespace rrr { +// Free helpers backing three pure predicates / statistics on the +// completion-tracker types: `CompletedEntry::is_expired`, +// `CompletionTracker::hit_rate`, `CompletionQueryResult::is_completed`. +// The last takes the `u8` discriminant of `CompletionStatus` +// (NOT_FOUND=0..EXPIRED=3); C++ casts at the boundary. Authored as +// inline Rust DSL. +#if RUSTYCPP_RUST +fn completion_entry_is_expired(current_time_ms: u64, timestamp_ms: u64, ttl_ms: u64) -> bool { + if ttl_ms == 0 { + return false; + } + current_time_ms > timestamp_ms + ttl_ms +} + +fn completion_tracker_hit_rate(query_hits: u64, queries: u64) -> f64 { + if queries == 0 { + return 0.0; + } + (query_hits as f64) / (queries as f64) +} + +fn completion_query_result_is_completed(status: u8) -> bool { + // COMPLETED == 1, COMPLETED_WITH_ERROR == 2 + status == 1 || status == 2 +} +#endif +/*RUSTYCPP:GEN-BEGIN id=completion_tracker.1 version=1 rust_sha256=144fdb9f1da11e09aae10f4177ac33278f0d8a9868a87f5148d43ea596e0dfc1*/ +bool completion_entry_is_expired(uint64_t current_time_ms, uint64_t timestamp_ms, uint64_t ttl_ms); +double completion_tracker_hit_rate(uint64_t query_hits, uint64_t queries); +bool completion_query_result_is_completed(uint8_t status); + +bool completion_entry_is_expired(uint64_t current_time_ms, uint64_t timestamp_ms, uint64_t ttl_ms) { + if (rusty::detail::deref_if_pointer_like(ttl_ms) == static_cast(0)) { + return false; + } + return rusty::detail::deref_if_pointer_like(current_time_ms) > (rusty::detail::deref_if_pointer_like(timestamp_ms) + rusty::detail::deref_if_pointer_like(ttl_ms)); +} + +double completion_tracker_hit_rate(uint64_t query_hits, uint64_t queries) { + if (rusty::detail::deref_if_pointer_like(queries) == static_cast(0)) { + return 0.0; + } + return ((static_cast(query_hits))) / ((static_cast(queries))); +} + +bool completion_query_result_is_completed(uint8_t status) { + return (rusty::detail::deref_if_pointer_like(status) == static_cast(1)) || (rusty::detail::deref_if_pointer_like(status) == static_cast(2)); +} +/*RUSTYCPP:GEN-END id=completion_tracker.1*/ + // =========================================================================== // CompletionTrackerConfig @@ -84,8 +134,7 @@ struct CompletedEntry { // @safe - Check if entry has expired bool is_expired(uint64_t current_time_ms, uint64_t ttl_ms) const { - if (ttl_ms == 0) return false; // No expiration - return current_time_ms > timestamp_ms + ttl_ms; + return completion_entry_is_expired(current_time_ms, timestamp_ms, ttl_ms); } }; @@ -281,9 +330,7 @@ class CompletionTracker { // @safe - Get hit rate (0.0 to 1.0) double hit_rate() const { - uint64_t q = queries_.get(); - if (q == 0) return 0.0; - return static_cast(query_hits_.get()) / static_cast(q); + return completion_tracker_hit_rate(query_hits_.get(), queries_.get()); } // @safe - Get eviction count @@ -379,8 +426,7 @@ struct CompletionQueryResult { // @safe - Check if completed (with or without error) bool is_completed() const { - return status == CompletionStatus::COMPLETED || - status == CompletionStatus::COMPLETED_WITH_ERROR; + return completion_query_result_is_completed(static_cast(status)); } }; From dff9df62a09e92a43b0bd8b63f51f34a226524e4 Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Thu, 28 May 2026 13:57:43 -0400 Subject: [PATCH 18/32] =?UTF-8?q?rrr/load=5Fbalancer:=20inline=20Rust=20DS?= =?UTF-8?q?L=20=E2=80=94=202=20pure-arithmetic=20helpers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert LoadBalancerState::next_round_robin_index and LoadBalancer::select_random to one multi-fn `#if RUSTYCPP_RUST` block. Both helpers take `u64`-modeled `size_t`s and assume `pool_size > 0` — C++ wrappers cast at the boundary and own the `pool_size == 0` / Cell read-modify-write logic. borrow_check_rrr_borrow_load_balancer clean, test_load_balancer 21/21 pass. Co-Authored-By: Claude Opus 4.7 --- docs/dev/rrr_rust_dsl_migration_plan.md | 8 ++++++ src/rrr/rpc/load_balancer.cpp | 34 ++++++++++++++++++++++--- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index 2eae61687..5ecb20123 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -262,6 +262,14 @@ keeping the C++ surface unchanged. boundary. rrr builds, borrow_check_rrr_borrow_completion_tracker clean, `test_completion_tracker` 27/27 pass. +- [x] `rpc/load_balancer.cpp::LoadBalancerState::next_round_robin_index`, + `LoadBalancer::select_random` — two pure-arithmetic helpers in + one multi-fn block (`lb_round_robin_next`, `lb_select_random`). + Both take `u64`-modeled `size_t`s; C++ wrappers cast at the + boundary (`size_t ↔ uint64_t`) and own the `pool_size == 0` / + Cell read-modify-write logic. rrr builds, + borrow_check_rrr_borrow_load_balancer clean, + `test_load_balancer` 21/21 pass. ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/rpc/load_balancer.cpp b/src/rrr/rpc/load_balancer.cpp index 8528365db..6ce789baa 100644 --- a/src/rrr/rpc/load_balancer.cpp +++ b/src/rrr/rpc/load_balancer.cpp @@ -1,8 +1,9 @@ module; +#include #include #include -#include +#include export module rrr.load_balancer; @@ -10,6 +11,33 @@ import std; export namespace rrr { +// Free helpers backing the two pure-arithmetic load-balancer +// strategies: round-robin advance (modular increment of the index) and +// random selection (`rand_value % pool_size`). Both helpers assume +// `pool_size > 0` — callers guard before invoking. Authored as inline +// Rust DSL. +#if RUSTYCPP_RUST +fn lb_round_robin_next(current: u64, pool_size: u64) -> u64 { + (current + 1) % pool_size +} + +fn lb_select_random(pool_size: u64, rand_value: u64) -> u64 { + rand_value % pool_size +} +#endif +/*RUSTYCPP:GEN-BEGIN id=load_balancer.1 version=1 rust_sha256=d031ecf07e239ed954fe492101beb924b0f06b67679c0796f93a6bf3a829c576*/ +uint64_t lb_round_robin_next(uint64_t current, uint64_t pool_size); +uint64_t lb_select_random(uint64_t pool_size, uint64_t rand_value); + +uint64_t lb_round_robin_next(uint64_t current, uint64_t pool_size) { + return ((rusty::detail::deref_if_pointer_like(current) + static_cast(1))) % rusty::detail::deref_if_pointer_like(pool_size); +} + +uint64_t lb_select_random(uint64_t pool_size, uint64_t rand_value) { + return rusty::detail::deref_if_pointer_like(rand_value) % rusty::detail::deref_if_pointer_like(pool_size); +} +/*RUSTYCPP:GEN-END id=load_balancer.1*/ + enum class LoadBalancingStrategy : uint8_t { RANDOM = 0, ROUND_ROBIN = 1, @@ -36,7 +64,7 @@ class LoadBalancerState { size_t next_round_robin_index(size_t pool_size) { if (pool_size == 0) return 0; size_t current = round_robin_index_.get(); - size_t next = (current + 1) % pool_size; + size_t next = static_cast(lb_round_robin_next(current, pool_size)); round_robin_index_.set(next); return current; } @@ -80,7 +108,7 @@ class LoadBalancer { private: static size_t select_random(size_t pool_size, size_t rand_value) { - return rand_value % pool_size; + return static_cast(lb_select_random(pool_size, rand_value)); } static size_t select_round_robin(size_t pool_size, LoadBalancerState& state) { From 2ce7addffdac61c346aeb3a5ce4fd204057d6a62 Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Thu, 28 May 2026 14:01:15 -0400 Subject: [PATCH 19/32] =?UTF-8?q?rrr/rand:=20inline=20Rust=20DSL=20?= =?UTF-8?q?=E2=80=94=203=20pure-arithmetic=20scaling=20helpers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert RandomGenerator::rand, rand_double, and nu_rand to one multi-fn `#if RUSTYCPP_RUST` block. Each helper takes the raw `rand_r` output as `i32` plus the user-supplied bounds. The rand_double helper takes RAND_MAX as a parameter to avoid the platform-dependent constant in Rust. C++ methods retain the `@unsafe` seed-fetch block and pass the scaled `r` into the helper. borrow_check_rrr_borrow_rand clean, rpcbench links (no dedicated test suite for rand). Co-Authored-By: Claude Opus 4.7 --- docs/dev/rrr_rust_dsl_migration_plan.md | 10 ++++++ src/rrr/misc/rand.cpp | 42 +++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index 5ecb20123..feecc2c0f 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -270,6 +270,16 @@ keeping the C++ surface unchanged. Cell read-modify-write logic. rrr builds, borrow_check_rrr_borrow_load_balancer clean, `test_load_balancer` 21/21 pass. +- [x] `misc/rand.cpp::RandomGenerator::rand`, `rand_double`, `nu_rand` + — three pure-arithmetic scaling helpers in one multi-fn block + (`rand_scale_to_range`, `rand_double_scale_to_range`, + `nu_rand_combine`). Each takes the raw `rand_r` output as `i32` + plus the user-supplied bounds. The `rand_double` helper takes + `RAND_MAX` as a parameter to avoid the platform-dependent + constant in Rust. C++ methods retain the `@unsafe` seed-fetch + block and pass the scaled `r` into the helper. rrr builds, + borrow_check_rrr_borrow_rand clean, rpcbench links (no + dedicated test suite for rand). ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/misc/rand.cpp b/src/rrr/misc/rand.cpp index 7a0e6b917..931a06566 100644 --- a/src/rrr/misc/rand.cpp +++ b/src/rrr/misc/rand.cpp @@ -1,5 +1,6 @@ module; +#include #include #include #include @@ -20,6 +21,41 @@ import rrr.debugging; // inline asm, malloc, and pthread C-API calls. export namespace rrr { +// Free helpers backing the pure-arithmetic scaling portions of +// `RandomGenerator::rand`, `rand_double`, and `nu_rand`. Each takes +// the raw `rand_r` output (i32) plus the user-supplied bounds and +// returns the scaled value. Authored as inline Rust DSL. +#if RUSTYCPP_RUST +fn rand_scale_to_range(r: i32, min: i32, max: i32) -> i32 { + (r % (max - min + 1)) + min +} + +fn rand_double_scale_to_range(r: i32, rand_max: i32, min: f64, max: f64) -> f64 { + (r as f64) / ((rand_max as f64) / (max - min)) + min +} + +fn nu_rand_combine(r1: i32, r2: i32, nu_constant: i32, x: i32, y: i32) -> i32 { + ((r1 | r2) + nu_constant) % (y - x + 1) + x +} +#endif +/*RUSTYCPP:GEN-BEGIN id=rand.1 version=1 rust_sha256=ceb6d63902c32008701f441b7ce7aceff0fa03473b7b6d6f7d77bb7aaea80035*/ +int32_t rand_scale_to_range(int32_t r, int32_t min, int32_t max); +double rand_double_scale_to_range(int32_t r, int32_t rand_max, double min, double max); +int32_t nu_rand_combine(int32_t r1, int32_t r2, int32_t nu_constant, int32_t x, int32_t y); + +int32_t rand_scale_to_range(int32_t r, int32_t min, int32_t max) { + return ((rusty::detail::deref_if_pointer_like(r) % (((rusty::detail::deref_if_pointer_like(max) - rusty::detail::deref_if_pointer_like(min)) + static_cast(1))))) + rusty::detail::deref_if_pointer_like(min); +} + +double rand_double_scale_to_range(int32_t r, int32_t rand_max, double min, double max) { + return (((static_cast(r))) / ((((static_cast(rand_max))) / ((rusty::detail::deref_if_pointer_like(max) - rusty::detail::deref_if_pointer_like(min)))))) + rusty::detail::deref_if_pointer_like(min); +} + +int32_t nu_rand_combine(int32_t r1, int32_t r2, int32_t nu_constant, int32_t x, int32_t y) { + return (((((rusty::detail::deref_if_pointer_like(r1) | rusty::detail::deref_if_pointer_like(r2))) + rusty::detail::deref_if_pointer_like(nu_constant))) % (((rusty::detail::deref_if_pointer_like(y) - rusty::detail::deref_if_pointer_like(x)) + static_cast(1)))) + rusty::detail::deref_if_pointer_like(x); +} +/*RUSTYCPP:GEN-END id=rand.1*/ + // @safe - see file header. class RandomGenerator { private: @@ -110,7 +146,7 @@ int RandomGenerator::rand(int min, int max) { r = rand_r(&seed_); #endif } - return (r % (max - min + 1)) + min; + return rand_scale_to_range(r, min, max); } double RandomGenerator::rand_double(double min, double max) { @@ -127,7 +163,7 @@ double RandomGenerator::rand_double(double min, double max) { r = rand_r(&seed_); #endif } - return (static_cast(r)) / (static_cast(RAND_MAX) / (max - min)) + min; + return rand_double_scale_to_range(r, RAND_MAX, min, max); } std::string RandomGenerator::rand_str(int length) { @@ -178,7 +214,7 @@ bool RandomGenerator::percentage_true(int p) { int RandomGenerator::nu_rand(int a, int x, int y) { int r1 = rand(0, a); int r2 = rand(x, y); - return ((r1 | r2) + nu_constant) % (y - x + 1) + x; + return nu_rand_combine(r1, r2, nu_constant, x, y); } // @unsafe - inline `rdtsc` asm + clock_gettime syscall. From 12c3f26aaa5f67d319149b7332ed2c9d0dac9aa8 Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Thu, 28 May 2026 14:05:48 -0400 Subject: [PATCH 20/32] =?UTF-8?q?rrr/request=5Fqueue:=20inline=20Rust=20DS?= =?UTF-8?q?L=20=E2=80=94=203=20pure-arithmetic=20helpers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert QueuedRequest::is_expired, QueuedRequest::age_ms, and RequestQueue::remaining_capacity to one multi-fn `#if RUSTYCPP_RUST` block. The first two helpers take `now_us` (fetched by the C++ caller via `clock_monotonic_us()`) plus the struct's timestamp_us/ttl_ms; the last takes the two `size_t`s the SpinMutex guard returned. borrow_check_rrr_borrow_request_queue clean, test_rpc_request_queue 30/30 pass. Co-Authored-By: Claude Opus 4.7 --- docs/dev/rrr_rust_dsl_migration_plan.md | 10 +++++ src/rrr/rpc/request_queue.cpp | 58 ++++++++++++++++++++++--- 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index feecc2c0f..9e22fe0ba 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -280,6 +280,16 @@ keeping the C++ surface unchanged. block and pass the scaled `r` into the helper. rrr builds, borrow_check_rrr_borrow_rand clean, rpcbench links (no dedicated test suite for rand). +- [x] `rpc/request_queue.cpp::QueuedRequest::is_expired`, + `QueuedRequest::age_ms`, `RequestQueue::remaining_capacity` — + three pure-arithmetic helpers in one multi-fn block + (`request_queue_is_expired`, `request_queue_age_ms`, + `request_queue_remaining_capacity`). The first two take `now_us` + (fetched by the C++ caller via `clock_monotonic_us()`) plus the + struct's `timestamp_us`/`ttl_ms`; the last takes the two + `size_t`s the SpinMutex guard returned. rrr builds, + borrow_check_rrr_borrow_request_queue clean, + `test_rpc_request_queue` 30/30 pass. ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/rpc/request_queue.cpp b/src/rrr/rpc/request_queue.cpp index ebccba9d1..01ec5ebeb 100644 --- a/src/rrr/rpc/request_queue.cpp +++ b/src/rrr/rpc/request_queue.cpp @@ -1,7 +1,7 @@ module; -#include -#include +#include +#include #include #include @@ -18,6 +18,51 @@ import rrr.threading; export namespace rrr { +// Free helpers backing the pure-arithmetic parts of +// `QueuedRequest::is_expired`, `QueuedRequest::age_ms`, and +// `RequestQueue::remaining_capacity`. All three are pure integer +// math; the first two take `now_us` (already fetched via the @unsafe +// monotonic-clock call), the last takes the two `size_t`s the +// SpinMutex guard returned. Authored as inline Rust DSL. +#if RUSTYCPP_RUST +fn request_queue_is_expired(now_us: u64, timestamp_us: u64, ttl_ms: u32) -> bool { + ((now_us - timestamp_us) / 1000) > (ttl_ms as u64) +} + +fn request_queue_age_ms(now_us: u64, timestamp_us: u64) -> u32 { + ((now_us - timestamp_us) / 1000) as u32 +} + +fn request_queue_remaining_capacity(max_size: u64, current_size: u64) -> u64 { + if max_size > current_size { + max_size - current_size + } else { + 0 + } +} +#endif +/*RUSTYCPP:GEN-BEGIN id=request_queue.1 version=1 rust_sha256=7893ef44ddca51df9fd007b58350900b6a3815cc639e2aa2b2cf261a187fd153*/ +bool request_queue_is_expired(uint64_t now_us, uint64_t timestamp_us, uint32_t ttl_ms); +uint32_t request_queue_age_ms(uint64_t now_us, uint64_t timestamp_us); +uint64_t request_queue_remaining_capacity(uint64_t max_size, uint64_t current_size); + +bool request_queue_is_expired(uint64_t now_us, uint64_t timestamp_us, uint32_t ttl_ms) { + return ((((rusty::detail::deref_if_pointer_like(now_us) - rusty::detail::deref_if_pointer_like(timestamp_us))) / 1000)) > ((static_cast(ttl_ms))); +} + +uint32_t request_queue_age_ms(uint64_t now_us, uint64_t timestamp_us) { + return static_cast((((rusty::detail::deref_if_pointer_like(now_us) - rusty::detail::deref_if_pointer_like(timestamp_us))) / 1000)); +} + +uint64_t request_queue_remaining_capacity(uint64_t max_size, uint64_t current_size) { + if (rusty::detail::deref_if_pointer_like(max_size) > rusty::detail::deref_if_pointer_like(current_size)) { + return rusty::detail::deref_if_pointer_like(max_size) - rusty::detail::deref_if_pointer_like(current_size); + } else { + return static_cast(0); + } +} +/*RUSTYCPP:GEN-END id=request_queue.1*/ + /** * Strategy for handling queue overflow. @@ -57,14 +102,13 @@ struct QueuedRequest { // @safe - delegates to rusty::sys::time::clock_monotonic_us. bool is_expired() const { const std::uint64_t now_us = rusty::sys::time::clock_monotonic_us(); - const std::uint64_t elapsed_us = now_us - timestamp_us; - return (elapsed_us / 1000) > ttl_ms; + return request_queue_is_expired(now_us, timestamp_us, ttl_ms); } // @safe - delegates to rusty::sys::time::clock_monotonic_us. uint32_t age_ms() const { const std::uint64_t now_us = rusty::sys::time::clock_monotonic_us(); - return static_cast((now_us - timestamp_us) / 1000); + return request_queue_age_ms(now_us, timestamp_us); } }; @@ -290,8 +334,8 @@ class RequestQueue { // @safe - SpinMutex::lock + VecDeque::size are @safe in the library. size_t remaining_capacity() const { auto guard = queue_.lock().unwrap(); - return config_.max_size > guard->size() ? - config_.max_size - guard->size() : 0; + return static_cast( + request_queue_remaining_capacity(config_.max_size, guard->size())); } // === Clear and Reset === From 30ac826e9ddd488f69d76921f7998307700d8e07 Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Thu, 28 May 2026 14:11:21 -0400 Subject: [PATCH 21/32] =?UTF-8?q?rrr/basetypes:=20inline=20Rust=20DSL=20?= =?UTF-8?q?=E2=80=94=20Timer::elapsed=20seconds=20helpers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert Timer::elapsed's two arithmetic branches to one multi-fn `#if RUSTYCPP_RUST` block (added as basetypes.3 after the existing SparseInt helpers). The live branch divides `now_us - begin_us` by 1e6; the stopped branch combines (sec, usec) pairs into seconds via i64 arithmetic. borrow_check_rrr_borrow_basetypes clean, rpcbench links (no dedicated test suite for Timer). Co-Authored-By: Claude Opus 4.7 --- docs/dev/rrr_rust_dsl_migration_plan.md | 9 +++++++ src/rrr/base/basetypes.cpp | 34 +++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index 9e22fe0ba..5efcc0f19 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -290,6 +290,15 @@ keeping the C++ surface unchanged. `size_t`s the SpinMutex guard returned. rrr builds, borrow_check_rrr_borrow_request_queue clean, `test_rpc_request_queue` 30/30 pass. +- [x] `base/basetypes.cpp::Timer::elapsed` (both branches) — two pure + seconds-conversion helpers in one multi-fn block + (`timer_elapsed_live_seconds`, + `timer_elapsed_stopped_seconds`). The live branch divides + `now_us - begin_us` by `1e6`; the stopped branch combines `(sec, + usec)` pairs into seconds. Added as `basetypes.3` (after the + existing `basetypes.1`/`basetypes.2` SparseInt helpers). rrr + builds, borrow_check_rrr_borrow_basetypes clean, rpcbench links + (no dedicated test suite for Timer). ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/base/basetypes.cpp b/src/rrr/base/basetypes.cpp index 14e88fe8f..5855f2060 100644 --- a/src/rrr/base/basetypes.cpp +++ b/src/rrr/base/basetypes.cpp @@ -531,6 +531,32 @@ void Timer::reset() { end_.tv_usec = 0; } +// Free helpers backing the two arithmetic branches of `Timer::elapsed`. +// The "live" branch (no stop() yet) divides the `now_us - begin_us` +// delta by 1e6; the "stopped" branch combines `(sec, usec)` pairs into +// seconds. Authored as inline Rust DSL. +#if RUSTYCPP_RUST +fn timer_elapsed_live_seconds(now_us: u64, begin_us: u64) -> f64 { + ((now_us - begin_us) as f64) / 1000000.0 +} + +fn timer_elapsed_stopped_seconds(begin_sec: i64, begin_usec: i64, end_sec: i64, end_usec: i64) -> f64 { + ((end_sec - begin_sec) as f64) + ((end_usec - begin_usec) as f64) / 1000000.0 +} +#endif +/*RUSTYCPP:GEN-BEGIN id=basetypes.3 version=1 rust_sha256=1e6f6fe57720959ad168d9fa8de32b0aa0ad784365f26b89d2151ff53f69f0c9*/ +double timer_elapsed_live_seconds(uint64_t now_us, uint64_t begin_us); +double timer_elapsed_stopped_seconds(int64_t begin_sec, int64_t begin_usec, int64_t end_sec, int64_t end_usec); + +double timer_elapsed_live_seconds(uint64_t now_us, uint64_t begin_us) { + return ((static_cast((rusty::detail::deref_if_pointer_like(now_us) - rusty::detail::deref_if_pointer_like(begin_us))))) / 1000000.0; +} + +double timer_elapsed_stopped_seconds(int64_t begin_sec, int64_t begin_usec, int64_t end_sec, int64_t end_usec) { + return ((static_cast((rusty::detail::deref_if_pointer_like(end_sec) - rusty::detail::deref_if_pointer_like(begin_sec))))) + (((static_cast((rusty::detail::deref_if_pointer_like(end_usec) - rusty::detail::deref_if_pointer_like(begin_usec))))) / 1000000.0); +} +/*RUSTYCPP:GEN-END id=basetypes.3*/ + // @safe - live-elapsed branch delegates to rusty::sys::time::gettimeofday_us. double Timer::elapsed() const { if (begin_.tv_sec == 0 && begin_.tv_usec == 0) std::abort(); @@ -538,9 +564,13 @@ double Timer::elapsed() const { const std::uint64_t now_us = rusty::sys::time::gettimeofday_us(); const std::uint64_t begin_us = static_cast(begin_.tv_sec) * 1000000 + begin_.tv_usec; - return static_cast(now_us - begin_us) / 1000000.0; + return timer_elapsed_live_seconds(now_us, begin_us); } - return end_.tv_sec - begin_.tv_sec + (end_.tv_usec - begin_.tv_usec) / 1000000.0; + return timer_elapsed_stopped_seconds( + static_cast(begin_.tv_sec), + static_cast(begin_.tv_usec), + static_cast(end_.tv_sec), + static_cast(end_.tv_usec)); } // @safe - all three seed contributors flow through @safe wrappers: From ec68891d96be1586f5937e2d14599eeceb293a1e Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Thu, 28 May 2026 14:15:53 -0400 Subject: [PATCH 22/32] =?UTF-8?q?rrr/request=5Foptions:=20inline=20Rust=20?= =?UTF-8?q?DSL=20=E2=80=94=20calculate=5Fdelay=5Fms=20backoff?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `request_calculate_delay_ms_base` as a second inline-Rust block in request_options.cpp. Replaces `base_delay_ms * std::pow(2.0, attempt)` with an iterative double-and-cap loop that saturates at `max_delay_ms` instead of overflowing for large attempts. The jitter step stays C++-side because it pulls a thread_local mt19937 sample. borrow_check_rrr_borrow_request_options clean, test_rpc_timeout_retry 36/36 pass. Co-Authored-By: Claude Opus 4.7 --- docs/dev/rrr_rust_dsl_migration_plan.md | 10 +++++ src/rrr/rpc/request_options.cpp | 55 ++++++++++++++++++++++--- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index 5efcc0f19..c43fa5f27 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -299,6 +299,16 @@ keeping the C++ surface unchanged. existing `basetypes.1`/`basetypes.2` SparseInt helpers). rrr builds, borrow_check_rrr_borrow_basetypes clean, rpcbench links (no dedicated test suite for Timer). +- [x] `rpc/request_options.cpp::RequestOptions::calculate_delay_ms` + (deterministic part) — exponential-backoff helper + `request_calculate_delay_ms_base` added as `request_options.2` + block (next to the existing `.1` predicate block). Replaces + `base_delay_ms * std::pow(2.0, attempt)` with an iterative + double-and-cap loop (saturates instead of overflowing for large + attempts). The jitter step stays C++-side because it pulls a + thread_local mt19937 sample. rrr builds, + borrow_check_rrr_borrow_request_options clean, + `test_rpc_timeout_retry` 36/36 pass. ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/rpc/request_options.cpp b/src/rrr/rpc/request_options.cpp index 5efe4253e..b35ab4352 100644 --- a/src/rrr/rpc/request_options.cpp +++ b/src/rrr/rpc/request_options.cpp @@ -67,6 +67,54 @@ uint64_t request_remaining_time_ms(uint64_t total_timeout_ms, uint64_t elapsed_m } /*RUSTYCPP:GEN-END id=request_options.1*/ +// Free helper backing the deterministic part of +// `RequestOptions::calculate_delay_ms`: exponential backoff capped at +// `max_delay_ms`. Equivalent to `base_delay_ms * 2^attempt`, computed +// iteratively so we can break early once the cap is hit (the +// `std::pow(2.0, attempt)` version overflows for large attempts, while +// the loop saturates). The jitter step stays C++-side because it +// pulls a thread_local mt19937 sample. Authored as inline Rust DSL. +#if RUSTYCPP_RUST +fn request_calculate_delay_ms_base(base_delay_ms: u16, max_delay_ms: u16, attempt: u16) -> f64 { + let mut delay: f64 = base_delay_ms as f64; + let max_delay: f64 = max_delay_ms as f64; + let mut i: u16 = 0; + while i < attempt { + delay *= 2.0; + if delay > max_delay { + delay = max_delay; + break; + } + i += 1; + } + if delay > max_delay { + delay = max_delay; + } + delay +} +#endif +/*RUSTYCPP:GEN-BEGIN id=request_options.2 version=1 rust_sha256=745ac5616cdfc66f01d4d42b26ba9b993965a91dcbac8a402eebf8ef7497c12a*/ +double request_calculate_delay_ms_base(uint16_t base_delay_ms, uint16_t max_delay_ms, uint16_t attempt); + +double request_calculate_delay_ms_base(uint16_t base_delay_ms, uint16_t max_delay_ms, uint16_t attempt) { + double delay = static_cast(base_delay_ms); + const double max_delay = static_cast(max_delay_ms); + uint16_t i = static_cast(0); + while (rusty::detail::deref_if_pointer_like(i) < rusty::detail::deref_if_pointer_like(attempt)) { + delay *= 2.0; + if (rusty::detail::deref_if_pointer_like(delay) > rusty::detail::deref_if_pointer_like(max_delay)) { + delay = std::move(max_delay); + break; + } + i += 1; + } + if (rusty::detail::deref_if_pointer_like(delay) > rusty::detail::deref_if_pointer_like(max_delay)) { + delay = std::move(max_delay); + } + return std::move(delay); +} +/*RUSTYCPP:GEN-END id=request_options.2*/ + struct RequestOptions { uint64_t timeout_ms = 1000; uint64_t total_timeout_ms = 0; @@ -130,11 +178,8 @@ struct RequestOptions { } uint64_t calculate_delay_ms(uint16_t attempt) const { - double delay = static_cast(base_delay_ms) * std::pow(2.0, attempt); - - if (delay > static_cast(max_delay_ms)) { - delay = static_cast(max_delay_ms); - } + double delay = request_calculate_delay_ms_base( + base_delay_ms, max_delay_ms, attempt); if (jitter_factor > 0.0f) { thread_local std::mt19937 gen(std::random_device{}()); From 6c159e368dbde0f160303829c4efe363fd35f21f Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Thu, 28 May 2026 14:21:00 -0400 Subject: [PATCH 23/32] =?UTF-8?q?rrr/frame=5Fcodec:=20inline=20Rust=20DSL?= =?UTF-8?q?=20=E2=80=94=203=20size/threshold=20helpers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert FrameHeader::total_frame_size, the payload-size validation shared by frame_codec_write_header / frame_codec_encode_into, and FrameStreamReader::compact_if_needed's threshold check to one multi-fn `#if RUSTYCPP_RUST` block. total_frame_size loses its constexpr qualifier (now a regular inline forwarder) — verified no callers use it in constexpr contexts. borrow_check_rrr_borrow_frame_codec clean, test_rpc_frame_codec 25/25 pass. Co-Authored-By: Claude Opus 4.7 --- docs/dev/rrr_rust_dsl_migration_plan.md | 10 ++++ src/rrr/rpc/frame_codec.cpp | 61 +++++++++++++++++++++---- 2 files changed, 61 insertions(+), 10 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index c43fa5f27..51710b7ae 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -309,6 +309,16 @@ keeping the C++ surface unchanged. thread_local mt19937 sample. rrr builds, borrow_check_rrr_borrow_request_options clean, `test_rpc_timeout_retry` 36/36 pass. +- [x] `rpc/frame_codec.cpp::FrameHeader::total_frame_size`, + `frame_codec_write_header`/`encode_into` payload-size + validation, and `FrameStreamReader::compact_if_needed` + threshold check — three trivial helpers in one multi-fn block + (`frame_header_total_size`, `frame_codec_payload_size_valid`, + `frame_codec_should_compact`). `total_frame_size` loses + `constexpr` (now a regular inline forwarder) — verified no + callers use it in constexpr contexts. rrr builds, + borrow_check_rrr_borrow_frame_codec clean, + `test_rpc_frame_codec` 25/25 pass. ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/rpc/frame_codec.cpp b/src/rrr/rpc/frame_codec.cpp index 4a3b50c03..bec666dd2 100644 --- a/src/rrr/rpc/frame_codec.cpp +++ b/src/rrr/rpc/frame_codec.cpp @@ -1,7 +1,9 @@ module; -#include -#include +#include +#include + +#include export module rrr.frame_codec; @@ -53,6 +55,42 @@ inline constexpr const char* frame_decode_status_to_string(FrameDecodeStatus s) return "Unknown"; } +// Free helpers backing three pure-arithmetic / range-check pieces of +// the frame codec: `FrameHeader::total_frame_size` (payload + 4-byte +// header), `frame_codec_encode_into`'s payload-size validation, and +// `FrameStreamReader::compact_if_needed`'s threshold check. Authored +// as inline Rust DSL. +#if RUSTYCPP_RUST +fn frame_header_total_size(payload_size: i32) -> i32 { + payload_size + 4 +} + +fn frame_codec_payload_size_valid(payload_size: i32, max_size: i32) -> bool { + payload_size >= 0 && payload_size <= max_size +} + +fn frame_codec_should_compact(read_pos: u64, threshold: u64) -> bool { + read_pos != 0 && read_pos >= threshold +} +#endif +/*RUSTYCPP:GEN-BEGIN id=frame_codec.1 version=1 rust_sha256=00c5af3d52b9278063ac134032b89ded2ded7b016b6bc523085fee261088c5c2*/ +int32_t frame_header_total_size(int32_t payload_size); +bool frame_codec_payload_size_valid(int32_t payload_size, int32_t max_size); +bool frame_codec_should_compact(uint64_t read_pos, uint64_t threshold); + +int32_t frame_header_total_size(int32_t payload_size) { + return rusty::detail::deref_if_pointer_like(payload_size) + static_cast(4); +} + +bool frame_codec_payload_size_valid(int32_t payload_size, int32_t max_size) { + return (rusty::detail::deref_if_pointer_like(payload_size) >= 0) && (rusty::detail::deref_if_pointer_like(payload_size) <= rusty::detail::deref_if_pointer_like(max_size)); +} + +bool frame_codec_should_compact(uint64_t read_pos, uint64_t threshold) { + return (rusty::detail::deref_if_pointer_like(read_pos) != static_cast(0)) && (rusty::detail::deref_if_pointer_like(read_pos) >= rusty::detail::deref_if_pointer_like(threshold)); +} +/*RUSTYCPP:GEN-END id=frame_codec.1*/ + // --------------------------------------------------------------------------- // Frame header // --------------------------------------------------------------------------- @@ -70,8 +108,8 @@ struct FrameHeader { std::int32_t payload_size = 0; bool extended_header_flag = false; - constexpr std::int32_t total_frame_size() const { - return payload_size + static_cast(kFrameHeaderSize); + std::int32_t total_frame_size() const { + return frame_header_total_size(payload_size); } }; @@ -93,8 +131,9 @@ inline bool frame_codec_write_header(std::uint8_t* out_buf, std::int32_t payload_size, bool extended_header_flag) { if (out_buf == nullptr) return false; - if (payload_size < 0) return false; - if (payload_size > kMaxFramePayloadSize) return false; + if (!frame_codec_payload_size_valid(payload_size, kMaxFramePayloadSize)) { + return false; + } const std::int32_t encoded = encode_response_size(payload_size, extended_header_flag); @@ -273,8 +312,9 @@ bool frame_codec_encode_into(std::vector& out, const std::uint8_t* payload, std::int32_t payload_size, bool extended_header_flag) { - if (payload_size < 0) return false; - if (payload_size > kMaxFramePayloadSize) return false; + if (!frame_codec_payload_size_valid(payload_size, kMaxFramePayloadSize)) { + return false; + } if (payload == nullptr && payload_size > 0) return false; const std::size_t prev_size = out.size(); @@ -361,8 +401,9 @@ std::size_t FrameStreamReader::buffered_bytes() const { // @unsafe - `std::memmove` from `buf_.data() + read_pos_` to `buf_.data()`. void FrameStreamReader::compact_if_needed() { - if (read_pos_ == 0) return; - if (read_pos_ < kCompactThresholdBytes) return; + if (!frame_codec_should_compact(read_pos_, kCompactThresholdBytes)) { + return; + } const std::size_t remaining = buf_.size() - read_pos_; if (remaining > 0) { From d86b28920b468c4fc0fe1e6b00a5a2bb056f3c18 Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Thu, 28 May 2026 14:26:51 -0400 Subject: [PATCH 24/32] =?UTF-8?q?rrr/misc:=20inline=20Rust=20DSL=20?= =?UTF-8?q?=E2=80=94=20FrequentJob=20period-elapsed=20predicate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert FrequentJob::Ready's period-elapsed check to a single-fn `#if RUSTYCPP_RUST` block (`frequent_job_period_elapsed`). Pure u64 arithmetic — `(now - last) > period`. C++ wrapper retains the `rrr::Time::now()` call and the `tm_last_` state mutation. borrow_check_rrr_borrow_misc clean, rpcbench links (no dedicated test suite for FrequentJob). Co-Authored-By: Claude Opus 4.7 --- docs/dev/rrr_rust_dsl_migration_plan.md | 6 ++++++ src/rrr/base/misc.cpp | 22 ++++++++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index 51710b7ae..ace8b5b23 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -319,6 +319,12 @@ keeping the C++ surface unchanged. callers use it in constexpr contexts. rrr builds, borrow_check_rrr_borrow_frame_codec clean, `test_rpc_frame_codec` 25/25 pass. +- [x] `base/misc.cpp::FrequentJob::Ready` — period-elapsed predicate + `frequent_job_period_elapsed` (single-fn block). Pure u64 + arithmetic; C++ wrapper retains the `rrr::Time::now()` call and + the `tm_last_` state mutation. rrr builds, + borrow_check_rrr_borrow_misc clean, rpcbench links (no + dedicated test suite for FrequentJob). ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/base/misc.cpp b/src/rrr/base/misc.cpp index 486f79bb1..4e921dbd9 100644 --- a/src/rrr/base/misc.cpp +++ b/src/rrr/base/misc.cpp @@ -1,7 +1,9 @@ module; +#include #include #include +#include #include #include @@ -98,6 +100,23 @@ class OneTimeJob : public Job { } }; +// Free helper backing the period-elapsed predicate in +// `FrequentJob::Ready`. Pure u64 arithmetic; the C++ wrapper owns the +// `rrr::Time::now()` call and the `tm_last_` mutation. Authored as +// inline Rust DSL. +#if RUSTYCPP_RUST +fn frequent_job_period_elapsed(now_us: u64, last_us: u64, period_us: u64) -> bool { + (now_us - last_us) > period_us +} +#endif +/*RUSTYCPP:GEN-BEGIN id=misc.1 version=1 rust_sha256=43bf2a5a69df6acff4e54d8c0c0b8d43bc5d55cee8f5979271d3017dc5dd3280*/ +bool frequent_job_period_elapsed(uint64_t now_us, uint64_t last_us, uint64_t period_us); + +bool frequent_job_period_elapsed(uint64_t now_us, uint64_t last_us, uint64_t period_us) { + return ((rusty::detail::deref_if_pointer_like(now_us) - rusty::detail::deref_if_pointer_like(last_us))) > rusty::detail::deref_if_pointer_like(period_us); +} +/*RUSTYCPP:GEN-END id=misc.1*/ + class FrequentJob : public Job { public: uint64_t tm_last_ = 0; @@ -107,8 +126,7 @@ class FrequentJob : public Job { // @safe - rrr::Time::now() flows through rusty::sys::time::clock_*_us. virtual bool Ready() override { uint64_t tm_now = rrr::Time::now(); - uint64_t s = tm_now - tm_last_; - if (s > period_) { + if (frequent_job_period_elapsed(tm_now, tm_last_, period_)) { tm_last_ = tm_now; return true; } From bc1340a6e0b9678992e677221b2128c697d5e8f4 Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Thu, 28 May 2026 14:31:04 -0400 Subject: [PATCH 25/32] =?UTF-8?q?rrr/server:=20inline=20Rust=20DSL=20?= =?UTF-8?q?=E2=80=94=20instance-id=20XOR-mix=20helper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert Server::Server's instance-id mixing step to a single-fn `#if RUSTYCPP_RUST` block (`server_mix_instance_id`). Pure u64 bit-twiddling: `(t ^ r ^ p) & i64::MAX`, force nonzero. The three input components (timestamp, random, pid-shifted) stay C++-side because their sources (std::chrono, std::random_device, rusty::sys::process::getpid) sit outside the inline-Rust world. First migration that lowers `i64::MAX as u64`. borrow_check_rrr_borrow_server clean, test_rpc_server_channel_binding 3/3 pass. Co-Authored-By: Claude Opus 4.7 --- docs/dev/rrr_rust_dsl_migration_plan.md | 9 +++++++ src/rrr/rpc/server.cpp | 35 ++++++++++++++++++++----- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index ace8b5b23..569cd0cdd 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -325,6 +325,15 @@ keeping the C++ surface unchanged. the `tm_last_` state mutation. rrr builds, borrow_check_rrr_borrow_misc clean, rpcbench links (no dedicated test suite for FrequentJob). +- [x] `rpc/server.cpp::Server::Server` instance-id mixing — + `server_mix_instance_id` (single-fn block). Pure u64 + bit-twiddling: `(t ^ r ^ p) & i64::MAX`, force nonzero. The + three input components (timestamp, random, pid-shifted) stay + C++-side because their sources (std::chrono, std::random_device, + rusty::sys::process::getpid) sit outside the inline-Rust world. + First migration that lowers `i64::MAX as u64`. rrr builds, + borrow_check_rrr_borrow_server clean, + `test_rpc_server_channel_binding` 3/3 pass. ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/rpc/server.cpp b/src/rrr/rpc/server.cpp index a29c2a56f..8fd5f7700 100644 --- a/src/rrr/rpc/server.cpp +++ b/src/rrr/rpc/server.cpp @@ -1243,6 +1243,33 @@ int DeferredReply::run_async(rusty::Function f) { return 0; } +// Free helper backing the XOR-mix-and-mask + force-nonzero step of +// `Server::Server`'s instance-id generation. Pure u64 bit-twiddling; +// the three input components (timestamp, random, pid-shifted) are +// built C++-side because their sources (std::chrono, std::random_device, +// rusty::sys::process::getpid) sit outside the inline-Rust world. +// Authored as inline Rust DSL. +#if RUSTYCPP_RUST +fn server_mix_instance_id(time_component: u64, random_component: u64, pid_component: u64) -> u64 { + let mixed: u64 = (time_component ^ random_component ^ pid_component) & (i64::MAX as u64); + if mixed == 0 { + return 1; + } + mixed +} +#endif +/*RUSTYCPP:GEN-BEGIN id=server.1 version=1 rust_sha256=c7d06cc5ea452e76273fb8cae1ada5608ac626e5b3cfe27ed52524436c539fa7*/ +uint64_t server_mix_instance_id(uint64_t time_component, uint64_t random_component, uint64_t pid_component); + +uint64_t server_mix_instance_id(uint64_t time_component, uint64_t random_component, uint64_t pid_component) { + uint64_t mixed = (((rusty::detail::deref_if_pointer_like(time_component) ^ rusty::detail::deref_if_pointer_like(random_component)) ^ rusty::detail::deref_if_pointer_like(pid_component))) & ((static_cast(std::numeric_limits::max()))); + if (rusty::detail::deref_if_pointer_like(mixed) == static_cast(0)) { + return static_cast(1); + } + return std::move(mixed); +} +/*RUSTYCPP:GEN-END id=server.1*/ + // @safe - Constructs server with PollThread // ctx_ starts as None; created in start() after all registrations Server::Server(rusty::Option> poll_thread_worker /* =... */) { @@ -1267,12 +1294,8 @@ Server::Server(rusty::Option> poll_thread_worker /* =... uint64_t pid_component = static_cast(rusty::sys::process::getpid()) << 48; - // Mix components with XOR for final ID - instance_id_ = (time_component ^ random_component ^ pid_component) - & static_cast(std::numeric_limits::max()); - if (instance_id_ == 0) { - instance_id_ = 1; - } + instance_id_ = server_mix_instance_id( + time_component, random_component, pid_component); Log_debug("Server: generated instance_id=%lu", instance_id_); } From 925bef17f9fc0793bbcc76053638e2582a581d14 Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Thu, 28 May 2026 14:37:02 -0400 Subject: [PATCH 26/32] =?UTF-8?q?rrr/connection=5Fmetrics:=20inline=20Rust?= =?UTF-8?q?=20DSL=20=E2=80=94=20min/max=20latency=20helpers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the existing connection_metrics.1 inline-Rust block with two new helpers (`metrics_new_min_latency_us`, `metrics_new_max_latency_us`) that collapse record_request_completed's per-axis "if sample is better, store sample" branches to a single Cell set. Block now holds 6 helpers total. borrow_check_rrr_borrow_connection_metrics clean, test_rpc_metrics 25/25 pass. Co-Authored-By: Claude Opus 4.7 --- docs/dev/rrr_rust_dsl_migration_plan.md | 9 +++++ src/rrr/rpc/connection_metrics.cpp | 47 +++++++++++++++++++------ 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index 569cd0cdd..a1fde8bfd 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -334,6 +334,15 @@ keeping the C++ surface unchanged. First migration that lowers `i64::MAX as u64`. rrr builds, borrow_check_rrr_borrow_server clean, `test_rpc_server_channel_binding` 3/3 pass. +- [x] `rpc/connection_metrics.cpp::record_request_completed` min/max + updates — added `metrics_new_min_latency_us` and + `metrics_new_max_latency_us` to the existing + `connection_metrics.1` block (now 6 helpers). Each is a pure + `if sample {<,>} current { sample } else { current }`. C++ code + collapses to one `min_latency_us_.set(metrics_new_min_..(...))` + per axis. rrr builds, + borrow_check_rrr_borrow_connection_metrics clean, + `test_rpc_metrics` 25/25 pass. ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/rpc/connection_metrics.cpp b/src/rrr/rpc/connection_metrics.cpp index ae2f4a631..3b09e8ace 100644 --- a/src/rrr/rpc/connection_metrics.cpp +++ b/src/rrr/rpc/connection_metrics.cpp @@ -13,7 +13,7 @@ import std; export namespace rrr { // Free helpers backing the derived-statistic methods on -// `ConnectionMetrics`. All four are pure `u64` math with sentinel +// `ConnectionMetrics`. All are pure `u64` math with sentinel // edge cases. C++ member methods read the Cells and forward. // Authored as inline Rust DSL. #if RUSTYCPP_RUST @@ -47,12 +47,28 @@ fn metrics_uptime_ms(connect_time_ms: u64, current_time_ms: u64) -> u64 { } current_time_ms - connect_time_ms } + +fn metrics_new_min_latency_us(current: u64, sample: u64) -> u64 { + if sample < current { + return sample; + } + current +} + +fn metrics_new_max_latency_us(current: u64, sample: u64) -> u64 { + if sample > current { + return sample; + } + current +} #endif -/*RUSTYCPP:GEN-BEGIN id=connection_metrics.1 version=1 rust_sha256=5914ef78b48145b14484486dfca96692f4814f202a02c689277974e6dd11c07e*/ +/*RUSTYCPP:GEN-BEGIN id=connection_metrics.1 version=1 rust_sha256=a6fa54d9d64ef7b4ab73a9389e71369554575dbb9f410e74dfcb9bbc2b4a1b46*/ uint64_t metrics_min_latency_us(uint64_t stored); uint64_t metrics_success_rate_percent(uint64_t completed, uint64_t total); uint64_t metrics_avg_latency_us(uint64_t total_latency_us, uint64_t completed); uint64_t metrics_uptime_ms(uint64_t connect_time_ms, uint64_t current_time_ms); +uint64_t metrics_new_min_latency_us(uint64_t current, uint64_t sample); +uint64_t metrics_new_max_latency_us(uint64_t current, uint64_t sample); uint64_t metrics_min_latency_us(uint64_t stored) { if (rusty::detail::deref_if_pointer_like(stored) == rusty::detail::deref_if_pointer_like(std::numeric_limits::max())) { @@ -84,6 +100,20 @@ uint64_t metrics_uptime_ms(uint64_t connect_time_ms, uint64_t current_time_ms) { } return rusty::detail::deref_if_pointer_like(current_time_ms) - rusty::detail::deref_if_pointer_like(connect_time_ms); } + +uint64_t metrics_new_min_latency_us(uint64_t current, uint64_t sample) { + if (rusty::detail::deref_if_pointer_like(sample) < rusty::detail::deref_if_pointer_like(current)) { + return std::move(sample); + } + return std::move(current); +} + +uint64_t metrics_new_max_latency_us(uint64_t current, uint64_t sample) { + if (rusty::detail::deref_if_pointer_like(sample) > rusty::detail::deref_if_pointer_like(current)) { + return std::move(sample); + } + return std::move(current); +} /*RUSTYCPP:GEN-END id=connection_metrics.1*/ class ConnectionMetrics { @@ -137,15 +167,10 @@ class ConnectionMetrics { decrement_in_flight(); total_latency_us_.set(total_latency_us_.get() + latency_us); - auto current_min = min_latency_us_.get(); - if (latency_us < current_min) { - min_latency_us_.set(latency_us); - } - - auto current_max = max_latency_us_.get(); - if (latency_us > current_max) { - max_latency_us_.set(latency_us); - } + min_latency_us_.set( + metrics_new_min_latency_us(min_latency_us_.get(), latency_us)); + max_latency_us_.set( + metrics_new_max_latency_us(max_latency_us_.get(), latency_us)); } void record_request_completed() const { From 078f6183cdb68d3cde32eb3553aecaabceaaa8f5 Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Thu, 28 May 2026 14:40:59 -0400 Subject: [PATCH 27/32] =?UTF-8?q?rrr/stat:=20inline=20Rust=20DSL=20?= =?UTF-8?q?=E2=80=94=20AvgStat::sample=20arithmetic=20helpers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert AvgStat::sample's per-sample mutations to one multi-fn `#if RUSTYCPP_RUST` block (`avg_stat_compute_avg`, `avg_stat_new_max`, `avg_stat_new_min`). `n_stat` is post-increment (always >= 1), so average division is safe. Min/max follow the same pattern as the connection_metrics latency helpers, on `i64` instead of `u64`. borrow_check_rrr_borrow_stat clean, rpcbench links (no dedicated test suite for AvgStat). Co-Authored-By: Claude Opus 4.7 --- docs/dev/rrr_rust_dsl_migration_plan.md | 8 ++++ src/rrr/misc/stat.cpp | 56 +++++++++++++++++++++++-- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index a1fde8bfd..a831991d5 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -343,6 +343,14 @@ keeping the C++ surface unchanged. per axis. rrr builds, borrow_check_rrr_borrow_connection_metrics clean, `test_rpc_metrics` 25/25 pass. +- [x] `misc/stat.cpp::AvgStat::sample` — three pure-arithmetic + helpers in one multi-fn block (`avg_stat_compute_avg`, + `avg_stat_new_max`, `avg_stat_new_min`). `n_stat` is + post-increment (always >= 1), so the average division is safe. + Min/max follow the same `if sample {<,>} current` pattern as + the connection_metrics latency helpers, on `i64` instead of + `u64`. rrr builds, borrow_check_rrr_borrow_stat clean, rpcbench + links (no dedicated test suite for AvgStat). ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/misc/stat.cpp b/src/rrr/misc/stat.cpp index 5ab60edcb..bca1f1152 100644 --- a/src/rrr/misc/stat.cpp +++ b/src/rrr/misc/stat.cpp @@ -1,6 +1,8 @@ module; -#include +#include + +#include export module rrr.stat; @@ -10,6 +12,52 @@ import std; // pointers, syscalls, or operator-overload chains. export namespace rrr { +// Free helpers backing the per-sample mutations in `AvgStat::sample`. +// `n_stat` is post-increment (always >= 1), so division is safe. +// Authored as inline Rust DSL. +#if RUSTYCPP_RUST +fn avg_stat_compute_avg(sum: i64, n_stat: i64) -> i64 { + sum / n_stat +} + +fn avg_stat_new_max(current_max: i64, sample: i64) -> i64 { + if sample > current_max { + return sample; + } + current_max +} + +fn avg_stat_new_min(current_min: i64, sample: i64) -> i64 { + if sample < current_min { + return sample; + } + current_min +} +#endif +/*RUSTYCPP:GEN-BEGIN id=stat.1 version=1 rust_sha256=45644572e67a6951f3c7b29b4ba1e398c71307098fa0f7d09eda33004ddca0c8*/ +int64_t avg_stat_compute_avg(int64_t sum, int64_t n_stat); +int64_t avg_stat_new_max(int64_t current_max, int64_t sample); +int64_t avg_stat_new_min(int64_t current_min, int64_t sample); + +int64_t avg_stat_compute_avg(int64_t sum, int64_t n_stat) { + return rusty::detail::deref_if_pointer_like(sum) / rusty::detail::deref_if_pointer_like(n_stat); +} + +int64_t avg_stat_new_max(int64_t current_max, int64_t sample) { + if (rusty::detail::deref_if_pointer_like(sample) > rusty::detail::deref_if_pointer_like(current_max)) { + return std::move(sample); + } + return std::move(current_max); +} + +int64_t avg_stat_new_min(int64_t current_min, int64_t sample) { + if (rusty::detail::deref_if_pointer_like(sample) < rusty::detail::deref_if_pointer_like(current_min)) { + return std::move(sample); + } + return std::move(current_min); +} +/*RUSTYCPP:GEN-END id=stat.1*/ + class AvgStat { public: int64_t n_stat_; @@ -23,9 +71,9 @@ class AvgStat { void sample(int64_t s = 1) { ++n_stat_; sum_ += s; - avg_ = sum_ / n_stat_; - max_ = s > max_ ? s : max_; - min_ = s < min_ ? s : min_; + avg_ = avg_stat_compute_avg(sum_, n_stat_); + max_ = avg_stat_new_max(max_, s); + min_ = avg_stat_new_min(min_, s); } void clear() { From 8fd83380be4cdc793dd332286813fbad2c6864d6 Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Thu, 28 May 2026 14:46:53 -0400 Subject: [PATCH 28/32] =?UTF-8?q?rrr/basetypes:=20inline=20Rust=20DSL=20?= =?UTF-8?q?=E2=80=94=20Rand::next(int,=20int)=20scaling=20helper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert Rand::next's scaling step to a single-fn `#if RUSTYCPP_RUST` block (`rand_next_in_range`). Pure u32 arithmetic mirroring the C++ implicit-conversion semantics: `(lower as u32) + r % ((upper - lower) as u32)`. New block inserted in the export namespace BEFORE the existing SparseInt/Timer impl-section helpers so the forward declaration is visible to the in-class `Rand::next` inline body — required renumbering existing basetypes.1/.2/.3 → .2/.3/.4 (per the auto-id-collision workaround). borrow_check_rrr_borrow_basetypes clean, rpcbench links. Co-Authored-By: Claude Opus 4.7 --- docs/dev/rrr_rust_dsl_migration_plan.md | 10 ++++++++ src/rrr/base/basetypes.cpp | 33 +++++++++++++++++++------ 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index a831991d5..68f9c97fe 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -351,6 +351,16 @@ keeping the C++ surface unchanged. the connection_metrics latency helpers, on `i64` instead of `u64`. rrr builds, borrow_check_rrr_borrow_stat clean, rpcbench links (no dedicated test suite for AvgStat). +- [x] `base/basetypes.cpp::Rand::next(int, int)` — + `rand_next_in_range` (single-fn block). Pure u32 scaling: + `(lower as u32) + r % ((upper - lower) as u32)`, mirroring the + C++ implicit-conversion semantics. New block needed to be + inserted BEFORE the existing SparseInt/Timer helpers (so the + forward declaration is visible to the in-class `Rand::next` + inline body) — required renumbering existing blocks + `basetypes.1`/`.2`/`.3` → `.2`/`.3`/`.4` (per the auto-id- + collision workaround). rrr builds, + borrow_check_rrr_borrow_basetypes clean, rpcbench links. ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/base/basetypes.cpp b/src/rrr/base/basetypes.cpp index 5855f2060..e8b21898a 100644 --- a/src/rrr/base/basetypes.cpp +++ b/src/rrr/base/basetypes.cpp @@ -133,6 +133,25 @@ class Timer { struct timeval end_; }; +// Free helper backing the scaling step of `Rand::next(int, int)`. +// Mirrors the C++ semantics: `lower + r % (upper - lower)` with all +// arithmetic in `u32` (`lower` cast at the boundary; for the typical +// non-negative `lower` this is a noop). `rand_()` returns `u32` +// (mt19937::result_type); we hand that in as `r`. Authored as inline +// Rust DSL. +#if RUSTYCPP_RUST +fn rand_next_in_range(r: u32, lower: i32, upper: i32) -> u32 { + (lower as u32) + r % ((upper - lower) as u32) +} +#endif +/*RUSTYCPP:GEN-BEGIN id=basetypes.1 version=1 rust_sha256=a26f2e98a47135212cbd04b373e24ea7f2ad4d057122d829166df62049f17180*/ +uint32_t rand_next_in_range(uint32_t r, int32_t lower, int32_t upper); + +uint32_t rand_next_in_range(uint32_t r, int32_t lower, int32_t upper) { + return ((static_cast(lower))) + (rusty::detail::deref_if_pointer_like(r) % ((static_cast((rusty::detail::deref_if_pointer_like(upper) - rusty::detail::deref_if_pointer_like(lower)))))); +} +/*RUSTYCPP:GEN-END id=basetypes.1*/ + class Rand: public NoCopy { std::mt19937 rand_; public: @@ -141,7 +160,7 @@ class Rand: public NoCopy { return rand_(); } std::mt19937::result_type next(int lower, int upper) { - return lower + rand_() % (upper - lower); + return rand_next_in_range(rand_(), lower, upper); } std::mt19937::result_type operator() () { return rand_(); @@ -243,7 +262,7 @@ fn sparse_int_buf_size_impl(byte0: i32) -> u64 { } } #endif -/*RUSTYCPP:GEN-BEGIN id=basetypes.1 version=1 rust_sha256=31569640ef4bafbff3f49ae55731162cc8754c42d9107ed3c43edc8fbc3594b8*/ +/*RUSTYCPP:GEN-BEGIN id=basetypes.2 version=1 rust_sha256=31569640ef4bafbff3f49ae55731162cc8754c42d9107ed3c43edc8fbc3594b8*/ uint64_t sparse_int_buf_size_impl(int32_t byte0); uint64_t sparse_int_buf_size_impl(int32_t byte0) { @@ -267,7 +286,7 @@ uint64_t sparse_int_buf_size_impl(int32_t byte0) { return static_cast(9); } } -/*RUSTYCPP:GEN-END id=basetypes.1*/ +/*RUSTYCPP:GEN-END id=basetypes.2*/ size_t SparseInt::buf_size(char byte0) { return static_cast( @@ -301,7 +320,7 @@ fn sparse_int_val_size_impl(val: i64) -> u64 { } } #endif -/*RUSTYCPP:GEN-BEGIN id=basetypes.2 version=1 rust_sha256=5e0232658d8bb791e952ff50617cc358eff1c2e8847a09b73d303ca99bb153d3*/ +/*RUSTYCPP:GEN-BEGIN id=basetypes.3 version=1 rust_sha256=5e0232658d8bb791e952ff50617cc358eff1c2e8847a09b73d303ca99bb153d3*/ uint64_t sparse_int_val_size_impl(int64_t val); uint64_t sparse_int_val_size_impl(int64_t val) { @@ -325,7 +344,7 @@ uint64_t sparse_int_val_size_impl(int64_t val) { return static_cast(9); } } -/*RUSTYCPP:GEN-END id=basetypes.2*/ +/*RUSTYCPP:GEN-END id=basetypes.3*/ size_t SparseInt::val_size(i64 val) { return static_cast(sparse_int_val_size_impl(val)); @@ -544,7 +563,7 @@ fn timer_elapsed_stopped_seconds(begin_sec: i64, begin_usec: i64, end_sec: i64, ((end_sec - begin_sec) as f64) + ((end_usec - begin_usec) as f64) / 1000000.0 } #endif -/*RUSTYCPP:GEN-BEGIN id=basetypes.3 version=1 rust_sha256=1e6f6fe57720959ad168d9fa8de32b0aa0ad784365f26b89d2151ff53f69f0c9*/ +/*RUSTYCPP:GEN-BEGIN id=basetypes.4 version=1 rust_sha256=1e6f6fe57720959ad168d9fa8de32b0aa0ad784365f26b89d2151ff53f69f0c9*/ double timer_elapsed_live_seconds(uint64_t now_us, uint64_t begin_us); double timer_elapsed_stopped_seconds(int64_t begin_sec, int64_t begin_usec, int64_t end_sec, int64_t end_usec); @@ -555,7 +574,7 @@ double timer_elapsed_live_seconds(uint64_t now_us, uint64_t begin_us) { double timer_elapsed_stopped_seconds(int64_t begin_sec, int64_t begin_usec, int64_t end_sec, int64_t end_usec) { return ((static_cast((rusty::detail::deref_if_pointer_like(end_sec) - rusty::detail::deref_if_pointer_like(begin_sec))))) + (((static_cast((rusty::detail::deref_if_pointer_like(end_usec) - rusty::detail::deref_if_pointer_like(begin_usec))))) / 1000000.0); } -/*RUSTYCPP:GEN-END id=basetypes.3*/ +/*RUSTYCPP:GEN-END id=basetypes.4*/ // @safe - live-elapsed branch delegates to rusty::sys::time::gettimeofday_us. double Timer::elapsed() const { From 0c5258f80a90471d09f5c19954b9ea535b05387b Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Thu, 28 May 2026 14:50:36 -0400 Subject: [PATCH 29/32] =?UTF-8?q?rrr/idempotency:=20inline=20Rust=20DSL=20?= =?UTF-8?q?=E2=80=94=20hash=20combiner=20helper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add idempotency_key_combine_hash to the existing idempotency.1 inline-Rust block (now 4 helpers). Pure u64 FNV-1a-style mixing: `h1 ^ (h2 * 0x9e3779b97f4a7c15)` (golden ratio constant). C++ wrapper still calls std::hash{} then passes the two hashes into the helper. borrow_check_rrr_borrow_idempotency clean, test_idempotency 32/32 pass. Co-Authored-By: Claude Opus 4.7 --- docs/dev/rrr_rust_dsl_migration_plan.md | 8 ++++++++ src/rrr/rpc/idempotency.cpp | 27 ++++++++++++++++++------- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index 68f9c97fe..b135c89bc 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -361,6 +361,14 @@ keeping the C++ surface unchanged. `basetypes.1`/`.2`/`.3` → `.2`/`.3`/`.4` (per the auto-id- collision workaround). rrr builds, borrow_check_rrr_borrow_basetypes clean, rpcbench links. +- [x] `rpc/idempotency.cpp::IdempotencyKeyHash::operator()` hash + combiner — added `idempotency_key_combine_hash` to the existing + `idempotency.1` block (now 4 helpers). Pure u64 FNV-1a-style + mixing: `h1 ^ (h2 * 0x9e3779b97f4a7c15)` (golden ratio + constant). C++ wrapper still calls `std::hash{}` then + passes the two hashes into the helper. rrr builds, + borrow_check_rrr_borrow_idempotency clean, `test_idempotency` + 32/32 pass. ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/rpc/idempotency.cpp b/src/rrr/rpc/idempotency.cpp index cf78731ef..3695c037f 100644 --- a/src/rrr/rpc/idempotency.cpp +++ b/src/rrr/rpc/idempotency.cpp @@ -20,11 +20,13 @@ import rrr.threading; export namespace rrr { -// Free helpers backing three pure predicates / statistics on the +// Free helpers backing pure predicates / statistics on the // idempotency types: `IdempotencyKey::is_valid`, -// `CachedResponse::is_expired`, and `IdempotencyCache::hit_rate`. All -// three take primitive `u64` arguments; C++ wrappers read struct -// fields / Cells and forward. Authored as inline Rust DSL. +// `CachedResponse::is_expired`, `IdempotencyCache::hit_rate`, plus +// the FNV-1a-style hash combiner for `IdempotencyKeyHash`. All take +// primitive `u64` arguments; C++ wrappers read struct fields / Cells +// (or call `std::hash` then pass the results in) and +// forward. Authored as inline Rust DSL. #if RUSTYCPP_RUST fn idempotency_key_is_valid(client_id: u64, sequence: u64) -> bool { client_id != 0 || sequence != 0 @@ -44,11 +46,17 @@ fn idempotency_cache_hit_rate(hits: u64, misses: u64) -> f64 { } (hits as f64) / (total as f64) } + +fn idempotency_key_combine_hash(h1: u64, h2: u64) -> u64 { + // Golden ratio constant for FNV-1a-style mixing. + h1 ^ (h2 * 0x9e3779b97f4a7c15) +} #endif -/*RUSTYCPP:GEN-BEGIN id=idempotency.1 version=1 rust_sha256=7c17b88f14f40486c4624285c442236be8075da9098d7fcc2f918aae7e303e97*/ +/*RUSTYCPP:GEN-BEGIN id=idempotency.1 version=1 rust_sha256=cc0f10273181d740a3bfc8e269b33c72fe23cd0e3e1c0688d963f6d2a5a53e1b*/ bool idempotency_key_is_valid(uint64_t client_id, uint64_t sequence); bool idempotency_response_is_expired(uint64_t current_time_ms, uint64_t timestamp_ms, uint64_t ttl_ms); double idempotency_cache_hit_rate(uint64_t hits, uint64_t misses); +uint64_t idempotency_key_combine_hash(uint64_t h1, uint64_t h2); bool idempotency_key_is_valid(uint64_t client_id, uint64_t sequence) { return (rusty::detail::deref_if_pointer_like(client_id) != static_cast(0)) || (rusty::detail::deref_if_pointer_like(sequence) != static_cast(0)); @@ -68,6 +76,10 @@ double idempotency_cache_hit_rate(uint64_t hits, uint64_t misses) { } return ((static_cast(hits))) / ((static_cast(total))); } + +uint64_t idempotency_key_combine_hash(uint64_t h1, uint64_t h2) { + return rusty::detail::deref_if_pointer_like(h1) ^ ((rusty::detail::deref_if_pointer_like(h2) * static_cast(11400714819323198485))); +} /*RUSTYCPP:GEN-END id=idempotency.1*/ @@ -117,10 +129,11 @@ struct IdempotencyKey { struct IdempotencyKeyHash { // @unsafe { hash computation } std::size_t operator()(const IdempotencyKey& key) const noexcept { - // Combine client_id and sequence using FNV-1a style mixing std::size_t h1 = std::hash{}(key.client_id); std::size_t h2 = std::hash{}(key.sequence); - return h1 ^ (h2 * 0x9e3779b97f4a7c15ULL); // Golden ratio constant + return static_cast( + idempotency_key_combine_hash( + static_cast(h1), static_cast(h2))); } }; From 9534757b95c6a5254d3a4b86ea720d0c26626daa Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Thu, 28 May 2026 14:56:03 -0400 Subject: [PATCH 30/32] =?UTF-8?q?rrr/circuit=5Fbreaker:=20inline=20Rust=20?= =?UTF-8?q?DSL=20=E2=80=94=20timespec=E2=86=92us=20helper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract the pure-arithmetic part of current_time_us into a new inline-Rust block (`circuit_timespec_to_us`). Added as the new circuit_breaker.1, with the existing circuit_should_probe renumbered to .2 (per the auto-id-collision workaround). C++ wrapper retains the `clock_gettime` syscall and feeds the `(sec, nsec)` pair into the helper. File-prefixed name avoids future link-time collision if heartbeat.cpp grows the same helper. borrow_check_rrr_borrow_circuit_breaker clean, test_rpc_circuit_breaker 21/21 pass. Co-Authored-By: Claude Opus 4.7 --- docs/dev/rrr_rust_dsl_migration_plan.md | 11 +++++++++++ src/rrr/rpc/circuit_breaker.cpp | 25 ++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index b135c89bc..e6b6a2c1c 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -369,6 +369,17 @@ keeping the C++ surface unchanged. passes the two hashes into the helper. rrr builds, borrow_check_rrr_borrow_idempotency clean, `test_idempotency` 32/32 pass. +- [x] `rpc/circuit_breaker.cpp::current_time_us` timespec→us + conversion — `circuit_timespec_to_us` (single-fn block, added + as new `circuit_breaker.1` ahead of the existing + `circuit_should_probe` — required renumbering existing `.1` → + `.2`). Pure u64: `tv_sec * 1_000_000 + tv_nsec / 1000`. C++ + wrapper retains the `clock_gettime` syscall and feeds the + resulting `(sec, nsec)` pair into the helper. File-prefixed + name (vs. plain `timespec_to_us`) avoids future link-time + collision if heartbeat.cpp grows the same helper. rrr builds, + borrow_check_rrr_borrow_circuit_breaker clean, + `test_rpc_circuit_breaker` 21/21 pass. ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/rpc/circuit_breaker.cpp b/src/rrr/rpc/circuit_breaker.cpp index 7a8d9f163..d4abc87b3 100644 --- a/src/rrr/rpc/circuit_breaker.cpp +++ b/src/rrr/rpc/circuit_breaker.cpp @@ -11,10 +11,29 @@ import std; export namespace rrr { +// Free helper backing the timespec→microseconds conversion used by +// `current_time_us`. Pure u64 arithmetic; the C++ wrapper owns the +// `clock_gettime` syscall and hands the resulting (sec, nsec) pair +// in. Authored as inline Rust DSL. +#if RUSTYCPP_RUST +fn circuit_timespec_to_us(tv_sec: u64, tv_nsec: u64) -> u64 { + tv_sec * 1000000 + tv_nsec / 1000 +} +#endif +/*RUSTYCPP:GEN-BEGIN id=circuit_breaker.1 version=1 rust_sha256=10383920c8512d3d9dcbe98b102b2d847e7bcb003ee67319933bd3b1354c887d*/ +uint64_t circuit_timespec_to_us(uint64_t tv_sec, uint64_t tv_nsec); + +uint64_t circuit_timespec_to_us(uint64_t tv_sec, uint64_t tv_nsec) { + return (rusty::detail::deref_if_pointer_like(tv_sec) * static_cast(1000000)) + (rusty::detail::deref_if_pointer_like(tv_nsec) / static_cast(1000)); +} +/*RUSTYCPP:GEN-END id=circuit_breaker.1*/ + inline uint64_t current_time_us() { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); - return static_cast(ts.tv_sec) * 1000000 + ts.tv_nsec / 1000; + return circuit_timespec_to_us( + static_cast(ts.tv_sec), + static_cast(ts.tv_nsec)); } enum class CircuitState : int { @@ -79,14 +98,14 @@ fn circuit_should_probe(now_us: u64, last_failure_us: u64, timeout_ms: u32) -> b now_us - last_failure_us >= timeout_us } #endif -/*RUSTYCPP:GEN-BEGIN id=circuit_breaker.1 version=1 rust_sha256=513b8ecf0d225fe0a92a55f713dc3bc14cd0c9025d89f408c9b1c22a5c02a5a7*/ +/*RUSTYCPP:GEN-BEGIN id=circuit_breaker.2 version=1 rust_sha256=513b8ecf0d225fe0a92a55f713dc3bc14cd0c9025d89f408c9b1c22a5c02a5a7*/ bool circuit_should_probe(uint64_t now_us, uint64_t last_failure_us, uint32_t timeout_ms); bool circuit_should_probe(uint64_t now_us, uint64_t last_failure_us, uint32_t timeout_ms) { const uint64_t timeout_us = ((static_cast(timeout_ms))) * static_cast(1000); return (rusty::detail::deref_if_pointer_like(now_us) - rusty::detail::deref_if_pointer_like(last_failure_us)) >= rusty::detail::deref_if_pointer_like(timeout_us); } -/*RUSTYCPP:GEN-END id=circuit_breaker.1*/ +/*RUSTYCPP:GEN-END id=circuit_breaker.2*/ // @safe - Single-threaded circuit breaker state machine. All fields are // rusty::Cell for trivially-copyable interior mutability; no raw From 4bdc50bd74b4406939af266c64de5343aab83691 Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Thu, 28 May 2026 15:00:13 -0400 Subject: [PATCH 31/32] =?UTF-8?q?rrr/heartbeat:=20inline=20Rust=20DSL=20?= =?UTF-8?q?=E2=80=94=20timespec=E2=86=92us=20helper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract the pure-arithmetic part of heartbeat_time_us into a new inline-Rust block (`heartbeat_timespec_to_us`). Added as new heartbeat.1 ahead of the existing 3 timing helpers, with those renumbered to .2. Identical body to circuit_timespec_to_us in circuit_breaker.cpp; file-prefixed name keeps the two separate at link time. borrow_check_rrr_borrow_heartbeat clean, test_rpc_heartbeat 20/20 pass. Co-Authored-By: Claude Opus 4.7 --- docs/dev/rrr_rust_dsl_migration_plan.md | 8 ++++++++ src/rrr/rpc/heartbeat.cpp | 27 ++++++++++++++++++++++--- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index e6b6a2c1c..c6cfa783b 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -380,6 +380,14 @@ keeping the C++ surface unchanged. collision if heartbeat.cpp grows the same helper. rrr builds, borrow_check_rrr_borrow_circuit_breaker clean, `test_rpc_circuit_breaker` 21/21 pass. +- [x] `rpc/heartbeat.cpp::heartbeat_time_us` timespec→us conversion + — `heartbeat_timespec_to_us` (single-fn block, added as new + `heartbeat.1` ahead of the existing 3 timing helpers — required + renumbering existing `.1` → `.2`). Identical body to + `circuit_timespec_to_us`; file-prefixed name keeps the two + separate at link time. rrr builds, + borrow_check_rrr_borrow_heartbeat clean, + `test_rpc_heartbeat` 20/20 pass. ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/rpc/heartbeat.cpp b/src/rrr/rpc/heartbeat.cpp index d9d62fd31..481d43092 100644 --- a/src/rrr/rpc/heartbeat.cpp +++ b/src/rrr/rpc/heartbeat.cpp @@ -13,10 +13,31 @@ import std; export namespace rrr { +// Free helper backing the timespec→microseconds conversion used by +// `heartbeat_time_us`. Pure u64 arithmetic; the C++ wrapper owns the +// `clock_gettime` syscall and hands the resulting (sec, nsec) pair +// in. File-prefixed name avoids link-time collision with the +// equivalent helper in circuit_breaker.cpp. Authored as inline Rust +// DSL. +#if RUSTYCPP_RUST +fn heartbeat_timespec_to_us(tv_sec: u64, tv_nsec: u64) -> u64 { + tv_sec * 1000000 + tv_nsec / 1000 +} +#endif +/*RUSTYCPP:GEN-BEGIN id=heartbeat.1 version=1 rust_sha256=017382a17e9a7e3c5b8333c349a1793c7ab03dbd3d61e49fb09278fec3c88038*/ +uint64_t heartbeat_timespec_to_us(uint64_t tv_sec, uint64_t tv_nsec); + +uint64_t heartbeat_timespec_to_us(uint64_t tv_sec, uint64_t tv_nsec) { + return (rusty::detail::deref_if_pointer_like(tv_sec) * static_cast(1000000)) + (rusty::detail::deref_if_pointer_like(tv_nsec) / static_cast(1000)); +} +/*RUSTYCPP:GEN-END id=heartbeat.1*/ + inline uint64_t heartbeat_time_us() { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); - return static_cast(ts.tv_sec) * 1000000 + ts.tv_nsec / 1000; + return heartbeat_timespec_to_us( + static_cast(ts.tv_sec), + static_cast(ts.tv_nsec)); } struct HeartbeatConfig { @@ -82,7 +103,7 @@ fn heartbeat_time_until_next_ms(now_us: u64, last_send_us: u64, interval_ms: u32 ((interval_us - elapsed_us) / 1000) as u32 } #endif -/*RUSTYCPP:GEN-BEGIN id=heartbeat.1 version=1 rust_sha256=ad0dc63f92a1763ba1d8538d2f3dcce52c56109bf1c6a96eec09b5ea43d3e67d*/ +/*RUSTYCPP:GEN-BEGIN id=heartbeat.2 version=1 rust_sha256=ad0dc63f92a1763ba1d8538d2f3dcce52c56109bf1c6a96eec09b5ea43d3e67d*/ bool heartbeat_interval_elapsed(uint64_t now_us, uint64_t last_send_us, uint32_t interval_ms); bool heartbeat_timeout_elapsed(uint64_t now_us, uint64_t last_send_us, uint32_t timeout_ms); uint32_t heartbeat_time_until_next_ms(uint64_t now_us, uint64_t last_send_us, uint32_t interval_ms); @@ -105,7 +126,7 @@ uint32_t heartbeat_time_until_next_ms(uint64_t now_us, uint64_t last_send_us, ui } return static_cast((((rusty::detail::deref_if_pointer_like(interval_us) - rusty::detail::deref_if_pointer_like(elapsed_us))) / 1000)); } -/*RUSTYCPP:GEN-END id=heartbeat.1*/ +/*RUSTYCPP:GEN-END id=heartbeat.2*/ // @safe - Heartbeat tracker. Fields are rusty::Cell for trivially- // copyable interior mutability + rusty::Function for the timeout From 6e75883bf61b4a85b3b29a87718806c8ef87a571 Mon Sep 17 00:00:00 2001 From: Shuai Mu Date: Thu, 28 May 2026 15:06:08 -0400 Subject: [PATCH 32/32] =?UTF-8?q?rrr/basetypes:=20inline=20Rust=20DSL=20?= =?UTF-8?q?=E2=80=94=20Timer::start/stop=20split=20helpers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert Timer::start/stop's us→(sec, usec) split to a new multi-fn `#if RUSTYCPP_RUST` block (`timer_us_to_sec`, `timer_us_to_usec_remainder`). The block is the inverse of the existing Timer::elapsed helpers — splitting microseconds back into a `time_t`/`suseconds_t` pair. Added as new basetypes.4 between SparseInt and Timer::elapsed; required renumbering existing Timer::elapsed block .4 → .5. borrow_check_rrr_borrow_basetypes clean, rpcbench links. Co-Authored-By: Claude Opus 4.7 --- docs/dev/rrr_rust_dsl_migration_plan.md | 8 ++++++ src/rrr/base/basetypes.cpp | 38 +++++++++++++++++++++---- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/docs/dev/rrr_rust_dsl_migration_plan.md b/docs/dev/rrr_rust_dsl_migration_plan.md index c6cfa783b..8bb61095b 100644 --- a/docs/dev/rrr_rust_dsl_migration_plan.md +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -388,6 +388,14 @@ keeping the C++ surface unchanged. separate at link time. rrr builds, borrow_check_rrr_borrow_heartbeat clean, `test_rpc_heartbeat` 20/20 pass. +- [x] `base/basetypes.cpp::Timer::start`/`stop` us→(sec, usec) split + — two pure-arithmetic helpers in one multi-fn block + (`timer_us_to_sec`, `timer_us_to_usec_remainder`). Added as new + `basetypes.4` between SparseInt and Timer::elapsed; required + renumbering the existing Timer::elapsed block `.4` → `.5`. + Each helper returns `i64` and the C++ wrapper casts to platform + `time_t`/`suseconds_t`. rrr builds, + borrow_check_rrr_borrow_basetypes clean, rpcbench links. ### Phase 2 — Leaf files - [ ] `src/rrr/base/debugging.cpp` diff --git a/src/rrr/base/basetypes.cpp b/src/rrr/base/basetypes.cpp index e8b21898a..1e1ec001a 100644 --- a/src/rrr/base/basetypes.cpp +++ b/src/rrr/base/basetypes.cpp @@ -523,6 +523,32 @@ i64 SparseInt::load_i64(const char* buf) { return val; } +// Free helpers backing the µs→(sec, usec) split used by +// `Timer::start` and `Timer::stop`. Pure u64 arithmetic; the C++ +// wrapper owns the `gettimeofday_us()` call and casts each result to +// the platform `time_t`/`suseconds_t`. Authored as inline Rust DSL. +#if RUSTYCPP_RUST +fn timer_us_to_sec(now_us: u64) -> i64 { + (now_us / 1000000) as i64 +} + +fn timer_us_to_usec_remainder(now_us: u64) -> i64 { + (now_us % 1000000) as i64 +} +#endif +/*RUSTYCPP:GEN-BEGIN id=basetypes.4 version=1 rust_sha256=98381e86d68026eefd3e00fd7861f8872ef1a52504193ff4c7cda8181159b66e*/ +int64_t timer_us_to_sec(uint64_t now_us); +int64_t timer_us_to_usec_remainder(uint64_t now_us); + +int64_t timer_us_to_sec(uint64_t now_us) { + return static_cast((rusty::detail::deref_if_pointer_like(now_us) / 1000000)); +} + +int64_t timer_us_to_usec_remainder(uint64_t now_us) { + return static_cast((rusty::detail::deref_if_pointer_like(now_us) % 1000000)); +} +/*RUSTYCPP:GEN-END id=basetypes.4*/ + Timer::Timer() : begin_(), end_() { reset(); } @@ -532,15 +558,15 @@ Timer::Timer() : begin_(), end_() { void Timer::start() { reset(); const std::uint64_t now = rusty::sys::time::gettimeofday_us(); - begin_.tv_sec = static_cast(now / 1000000); - begin_.tv_usec = static_cast(now % 1000000); + begin_.tv_sec = static_cast(timer_us_to_sec(now)); + begin_.tv_usec = static_cast(timer_us_to_usec_remainder(now)); } // @safe - delegates to rusty::sys::time::gettimeofday_us. void Timer::stop() { const std::uint64_t now = rusty::sys::time::gettimeofday_us(); - end_.tv_sec = static_cast(now / 1000000); - end_.tv_usec = static_cast(now % 1000000); + end_.tv_sec = static_cast(timer_us_to_sec(now)); + end_.tv_usec = static_cast(timer_us_to_usec_remainder(now)); } void Timer::reset() { @@ -563,7 +589,7 @@ fn timer_elapsed_stopped_seconds(begin_sec: i64, begin_usec: i64, end_sec: i64, ((end_sec - begin_sec) as f64) + ((end_usec - begin_usec) as f64) / 1000000.0 } #endif -/*RUSTYCPP:GEN-BEGIN id=basetypes.4 version=1 rust_sha256=1e6f6fe57720959ad168d9fa8de32b0aa0ad784365f26b89d2151ff53f69f0c9*/ +/*RUSTYCPP:GEN-BEGIN id=basetypes.5 version=1 rust_sha256=1e6f6fe57720959ad168d9fa8de32b0aa0ad784365f26b89d2151ff53f69f0c9*/ double timer_elapsed_live_seconds(uint64_t now_us, uint64_t begin_us); double timer_elapsed_stopped_seconds(int64_t begin_sec, int64_t begin_usec, int64_t end_sec, int64_t end_usec); @@ -574,7 +600,7 @@ double timer_elapsed_live_seconds(uint64_t now_us, uint64_t begin_us) { double timer_elapsed_stopped_seconds(int64_t begin_sec, int64_t begin_usec, int64_t end_sec, int64_t end_usec) { return ((static_cast((rusty::detail::deref_if_pointer_like(end_sec) - rusty::detail::deref_if_pointer_like(begin_sec))))) + (((static_cast((rusty::detail::deref_if_pointer_like(end_usec) - rusty::detail::deref_if_pointer_like(begin_usec))))) / 1000000.0); } -/*RUSTYCPP:GEN-END id=basetypes.4*/ +/*RUSTYCPP:GEN-END id=basetypes.5*/ // @safe - live-elapsed branch delegates to rusty::sys::time::gettimeofday_us. double Timer::elapsed() const {