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..8bb61095b --- /dev/null +++ b/docs/dev/rrr_rust_dsl_migration_plan.md @@ -0,0 +1,419 @@ +# rrr Rust DSL Migration Plan + +## Goal + +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. +- **Behavior**: existing GTest binaries pass; representative + 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` +- 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 \ + inline-rust --check --files src/rrr/.cpp + ``` + +## Per-iteration protocol + +1. **Pick** the next unchecked item from the Progress log. +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_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 + +- 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 + +- **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. +- **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. +- **`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". +- **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 + +### Phase 0 — Tooling +- [x] Build transpiler release binary +- [x] Smoke-test transpiler on a toy file +- [x] Author this plan doc + +### 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. +- [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. +- [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. +- [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. +- [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. +- [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. +- [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. +- [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. +- [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. +- [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. +- [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. +- [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. +- [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. +- [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. +- [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. +- [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. +- [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). +- [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. +- [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). +- [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. +- [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. +- [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). +- [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. +- [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. +- [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). +- [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. +- [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. +- [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. +- [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. +- [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` +- [ ] `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). diff --git a/src/rrr/base/basetypes.cpp b/src/rrr/base/basetypes.cpp index 9e18f3238..1e1ec001a 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_(); @@ -217,49 +236,119 @@ 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.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) { + 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.2*/ -size_t SparseInt::val_size(i64 val) { - if (-64 <= val && val <= 63) { +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 +// 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.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) { + 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.3*/ + +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) { @@ -434,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(); } @@ -443,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() { @@ -461,6 +576,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.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); + +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.5*/ + // @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(); @@ -468,9 +609,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: 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; } 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. 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() { diff --git a/src/rrr/rpc/circuit_breaker.cpp b/src/rrr/rpc/circuit_breaker.cpp index 15a802d87..d4abc87b3 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; @@ -10,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 { @@ -69,6 +89,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.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.2*/ + // @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 +146,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; 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)); } }; diff --git a/src/rrr/rpc/connection_metrics.cpp b/src/rrr/rpc/connection_metrics.cpp index 245bc1ff2..3b09e8ace 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,110 @@ 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 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 +} + +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=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())) { + 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); +} + +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 { public: ConnectionMetrics() = default; @@ -34,29 +139,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 { @@ -69,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 { 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)); } }; diff --git a/src/rrr/rpc/errors.cpp b/src/rrr/rpc/errors.cpp index a2dbcea70..f6873c297 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,37 +109,155 @@ 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))); +} + +// 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=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 get_error_category(err) == RpcErrorCategory::CONNECTION; + return rpc_error_is_connection_code(static_cast(err)); } inline bool is_timeout_error(RpcError err) { - return get_error_category(err) == RpcErrorCategory::TIMEOUT; + return rpc_error_is_timeout_code(static_cast(err)); } -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.4 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.4*/ + +inline bool is_retryable_error(RpcError err) { + return rpc_error_is_retryable(static_cast(err)); } } // export namespace rrr 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) { diff --git a/src/rrr/rpc/heartbeat.cpp b/src/rrr/rpc/heartbeat.cpp index 77d2da3ad..481d43092 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; @@ -12,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 { @@ -56,6 +78,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.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); + +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.2*/ + // @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 +167,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 +198,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 +224,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 { diff --git a/src/rrr/rpc/idempotency.cpp b/src/rrr/rpc/idempotency.cpp index 3a89f227f..3695c037f 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,68 @@ import rrr.threading; export namespace rrr { +// Free helpers backing pure predicates / statistics on the +// idempotency types: `IdempotencyKey::is_valid`, +// `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 +} + +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) +} + +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=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)); +} + +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))); +} + +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*/ + // =========================================================================== // IdempotencyKey @@ -54,7 +116,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 @@ -67,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))); } }; @@ -164,8 +227,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 +520,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 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 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) { diff --git a/src/rrr/rpc/reconnect_policy.cpp b/src/rrr/rpc/reconnect_policy.cpp index bb4a22b70..6aa0c2601 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,102 @@ 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 +} + +// 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=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) { + 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); +} + +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 { private: const ReconnectPolicy& policy_; @@ -74,30 +171,23 @@ 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() { 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); @@ -108,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() { @@ -133,13 +214,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()); } }; diff --git a/src/rrr/rpc/request_options.cpp b/src/rrr/rpc/request_options.cpp index 8284407b0..b35ab4352 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,101 @@ 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*/ + +// 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; @@ -78,15 +174,12 @@ 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 { - 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{}()); @@ -104,17 +197,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); } }; 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 === 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_); }