Skip to content

refactor(consensus)!: promote AdvanceOutOfRange to a typed variant#571

Closed
SebastianThiebaud wants to merge 1 commit into
mainfrom
worktree-refactor+advance-out-of-range-variant
Closed

refactor(consensus)!: promote AdvanceOutOfRange to a typed variant#571
SebastianThiebaud wants to merge 1 commit into
mainfrom
worktree-refactor+advance-out-of-range-variant

Conversation

@SebastianThiebaud
Copy link
Copy Markdown
Contributor

Summary

  • The shared pre-persist range guard was packing its rejection through ConsensusError::PermanentDriver(Box::new(AdvanceOutOfRange(_))), forcing every consumer that wanted to identify this one well-known condition to downcast_ref (and a test to expect() that the downcast succeeded — a contract assertion the type system should enforce for free).
  • Promotes the condition to its own variant ConsensusError::AdvanceOutOfRange { at_least: u64 } and threads the typed value end-to-end:
    • reject_out_of_range_advance returns the variant directly — no Box::new, no string formatting at the boundary.
    • PersistDisposition gains a matching AdvanceOutOfRange { at_least } variant so the server-side classifier does not re-box into PersistDisposition::Permanent(Box<dyn Error>).
    • service::extend_window and fence::run_leader_watch get a new compiler-forced arm. Wire disposition is unchanged (INTERNAL, must-not-retry — exactly what PermanentDriver produced before); the gRPC status message now names the offending at_least value directly instead of routing it through source.to_string().
  • Removes the now-unused standalone pub struct AdvanceOutOfRange(pub u64) and its pub use. The three driver-level tests that previously matched ConsensusError::PermanentDriver(_) for the range-guard case now match the typed variant including the surfaced at_least value; the four PermanentDriver(_) matchers covering real driver faults (I/O failure, fsync panic, apply-task-gone) are left alone — they continue to test what they were meant to test.

After this change, every remaining Box<dyn std::error::Error + Send + Sync> carried by ConsensusError is genuinely-opaque driver internals (rocksdb errors, tonic errors, omnipaxos errors, file I/O) — the closed-shape AdvanceOutOfRange no longer hides in there.

Breaking change

ConsensusError is not #[non_exhaustive], so adding a variant is a SemVer break for external pattern matchers. The previously re-exported tsoracle_consensus::AdvanceOutOfRange struct is removed — match on ConsensusError::AdvanceOutOfRange { at_least } instead. No in-repo callers imported the struct directly, so the in-tree migration is mechanical.

Test plan

  • cargo build --workspace --all-features — clean
  • cargo test --workspace --all-features — zero failures
  • cargo clippy --workspace --all-features --all-targets -- -D warnings — clean
  • cargo fmt --check — clean (pre-commit hook enforced)
  • New unit test reject_out_of_range_advance_rejects_above_the_cap_as_typed_variant matches the variant with the exact at_least value (no downcast_ref)
  • New disposition test advance_out_of_range_threads_typed_value_through_classify proves the value flows typed across the consensus → server boundary without re-boxing
  • Driver-level guard tests in file/openraft/paxos now assert ConsensusError::AdvanceOutOfRange { at_least } if at_least == PHYSICAL_MS_MAX + 1 (stronger than the previous matches!(_, PermanentDriver(_)))

The shared pre-persist range guard previously surfaced its rejection
through `ConsensusError::PermanentDriver(Box::new(AdvanceOutOfRange(_)))`,
forcing every consumer that wanted to identify this single well-known
condition to `downcast_ref` (and a test to `expect()` that the downcast
succeeded — a contract assertion the type system should enforce for free).

Promotes the condition to its own variant `ConsensusError::AdvanceOutOfRange
{ at_least: u64 }` and threads the typed value end-to-end:

  * `reject_out_of_range_advance` returns the variant directly — no
    `Box::new`, no string formatting at the boundary.
  * `PersistDisposition` gains a matching `AdvanceOutOfRange { at_least }`
    variant so the server-side classifier does NOT re-box into
    `PersistDisposition::Permanent(Box<dyn Error>)`.
  * `service::extend_window` and `fence::run_leader_watch` get a new
    compiler-forced arm. Wire disposition is unchanged (INTERNAL,
    must-not-retry — same as `PermanentDriver` produced before); the gRPC
    status message now names the offending `at_least` value directly
    instead of routing it through `source.to_string()`.
  * Removes the now-unused standalone `pub struct AdvanceOutOfRange(pub
    u64)` (and its `pub use`). The three driver tests that previously
    matched `ConsensusError::PermanentDriver(_)` for the range-guard case
    now match the typed variant including the surfaced `at_least` value;
    the four `PermanentDriver(_)` matchers covering real driver faults
    (I/O failure, fsync panic, apply-task-gone) are left alone.

BREAKING CHANGE: `ConsensusError` is not `#[non_exhaustive]`, so adding a
variant is a SemVer break for external pattern matchers. The previously
re-exported `tsoracle_consensus::AdvanceOutOfRange` struct is removed —
match on `ConsensusError::AdvanceOutOfRange { at_least }` instead.

Signed-off-by: Sebastian Thiebaud <sebastian@prismarisk.com>
@SebastianThiebaud
Copy link
Copy Markdown
Contributor Author

Superseded by #569, which landed the same refactor (typed end-to-end) on main first. Closing in favor of the merged variant.

@SebastianThiebaud SebastianThiebaud deleted the worktree-refactor+advance-out-of-range-variant branch May 27, 2026 06:25
@coveralls
Copy link
Copy Markdown

Coverage Report for CI Build 26494519362

Warning

Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes.
Quick fix: rebase this PR. Learn more →

Coverage decreased (-0.05%) to 94.857%

Details

  • Coverage decreased (-0.05%) from the base build.
  • Patch coverage: 12 uncovered changes across 6 files (29 of 41 lines covered, 70.73%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
crates/tsoracle-server/src/fence.rs 4 0 0.0%
crates/tsoracle-server/src/service.rs 4 0 0.0%
crates/tsoracle-consensus/src/advance.rs 5 4 80.0%
crates/tsoracle-driver-openraft/src/driver.rs 5 4 80.0%
crates/tsoracle-driver-paxos/src/driver.rs 4 3 75.0%
crates/tsoracle-server/src/persist_disposition.rs 8 7 87.5%
Total (8 files) 41 29 70.73%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 14971
Covered Lines: 14201
Line Coverage: 94.86%
Coverage Strength: 378995.02 hits per line

💛 - Coveralls

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants