Skip to content

fix(rollback): make topology mutations failure-atomic#480

Merged
acgetchell merged 2 commits into
mainfrom
refactor/364-rollback-primitives
Jun 26, 2026
Merged

fix(rollback): make topology mutations failure-atomic#480
acgetchell merged 2 commits into
mainfrom
refactor/364-rollback-primitives

Conversation

@acgetchell

Copy link
Copy Markdown
Owner
  • Add scoped rollback guards for TDS, generic triangulation, and Delaunay mutation windows.
  • Use the guards across insertion, deletion, local facet repair, and flip-repair retry paths.
  • Restore insertion state and spatial-index caches according to the Delaunay rollback policy.
  • Document rollback ownership rules for future mutation paths.

- Add scoped rollback guards for TDS, generic triangulation, and Delaunay mutation windows.
- Use the guards across insertion, deletion, local facet repair, and flip-repair retry paths.
- Restore insertion state and spatial-index caches according to the Delaunay rollback policy.
- Document rollback ownership rules for future mutation paths.
@acgetchell acgetchell self-assigned this Jun 26, 2026
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 9261001e-30a3-4bc1-a522-4f48900f4b38

📥 Commits

Reviewing files that changed from the base of the PR and between 8803872 and c303f44.

📒 Files selected for processing (7)
  • docs/dev/rust.md
  • src/core/repair.rs
  • src/core/rollback.rs
  • src/core/tds/rollback.rs
  • src/delaunay/insertion.rs
  • src/delaunay/rollback.rs
  • src/lib.rs
✅ Files skipped from review due to trivial changes (1)
  • docs/dev/rust.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/core/rollback.rs
  • src/delaunay/rollback.rs
  • src/delaunay/insertion.rs
  • src/core/repair.rs

Walkthrough

Adds scoped rollback guards for Tds, Triangulation, and DelaunayTriangulation, rewires insertion, repair, and deletion flows to use commit/rollback transactions, and updates docs, tests, and module exports.

Changes

Rollback transaction migration

Layer / File(s) Summary
Rollback primitives and wiring
src/core/tds/rollback.rs, src/core/rollback.rs, src/lib.rs, src/core/tds/storage.rs, docs/dev/rust.md
Tds and Triangulation rollback guards add scoped restore, commit, and rollback behavior, are wired through module exports, and are covered by storage and guard tests plus docs.
Core mutation paths
src/core/insertion.rs, src/core/repair.rs, src/core/algorithms/flips.rs
Insertion, vertex removal, facet repair, and flip-based repair now run inside rollback transactions instead of cloning and restoring triangulation state on error.
Delaunay rollback guard
src/delaunay/rollback.rs, src/lib.rs
DelaunayRollbackTransaction captures insertion state, spatial-index rollback policy, and nested TDS rollback behavior, with tests for restore, invalidate, commit, and rollback flows.
Delaunay insertion and deletion
src/delaunay/insertion.rs, src/delaunay/deletion.rs
Delaunay insertion and deletion now run their mutation, repair, and validation steps through the Delaunay rollback transaction and commit or roll back based on the result.

Sequence Diagram(s)

Triangulation rollback flow

sequenceDiagram
  participant TdsOwnerRollbackTransaction
  participant Tds
  participant TriangulationRollbackTransaction
  TdsOwnerRollbackTransaction->>Tds: capture canonical rollback state
  TriangulationRollbackTransaction->>TdsOwnerRollbackTransaction: begin(owner)
  TriangulationRollbackTransaction->>TriangulationRollbackTransaction: triangulation_mut() mutate
  alt success
    TriangulationRollbackTransaction->>TdsOwnerRollbackTransaction: commit()
  else failure or drop
    TriangulationRollbackTransaction->>TdsOwnerRollbackTransaction: restore()
  end
Loading

Delaunay rollback flow

sequenceDiagram
  participant DelaunayRollbackTransaction
  participant DelaunayTriangulation
  participant TdsOwnerRollbackTransaction
  DelaunayRollbackTransaction->>TdsOwnerRollbackTransaction: begin(owner)
  DelaunayRollbackTransaction->>DelaunayTriangulation: save insertion_state and spatial index
  DelaunayRollbackTransaction->>DelaunayTriangulation: delaunay_mut() mutate
  alt success
    DelaunayRollbackTransaction->>TdsOwnerRollbackTransaction: commit()
  else failure or drop
    DelaunayRollbackTransaction->>DelaunayTriangulation: restore()
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90+ minutes

Possibly related issues

Possibly related PRs

  • acgetchell/delaunay#376 — Direct predecessor work on rollback snapshot and transaction semantics that this PR extends and replaces.
  • acgetchell/delaunay#401 — Overlaps with the remove_vertex_with_fan_retriangulation repair path that now runs inside TriangulationRollbackTransaction.
  • acgetchell/delaunay#124 — Shares the rollback-based deletion and vertex-removal control flow that this PR updates again.

Suggested labels

bug, rust, api, topology

Poem

A bunny hopped through rollback doors,
And saved each hop from tumble floors.
Tds and triangles bowed just right,
Then Delaunay leapt back into light.
🐇✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: making topology mutations failure-atomic via rollback.
Description check ✅ Passed The description directly matches the changeset and summarizes the rollback guards, mutation paths, and documentation updates.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 100.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/364-rollback-primitives

Comment @coderabbitai help to get the list of available commands.

@codacy-production

codacy-production Bot commented Jun 26, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 57 complexity

Metric Results
Complexity 57

View in Codacy

🟢 Coverage 93.95% diff coverage · +0.04% coverage variation

Metric Results
Coverage variation +0.04% coverage variation (-1.00%)
Diff coverage 93.95% diff coverage

View coverage diff in Codacy

Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (60e99d1) 73579 67459 91.68%
Head commit (c303f44) 74123 (+544) 67988 (+529) 91.72% (+0.04%)

Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>

Diff coverage details
Coverable lines Covered lines Diff coverage
Pull request (#480) 810 761 93.95%

Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@coderabbitai coderabbitai Bot added api bug Something isn't working rust Pull requests that update rust code topology labels Jun 26, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
src/core/rollback.rs (1)

90-139: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Expand these rollback tests to 4D and 5D.

TriangulationRollbackTransaction is const-generic over D, but the new coverage only exercises 2D and 3D. A small dimension macro here would bring this file in line with the repo’s dimension-generic test policy.

As per coding guidelines, **/*.{rs,md}: Tests should verify mathematical, geometric, and topological invariants; dimension-generic tests should cover 2D through 5D whenever feasible.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/rollback.rs` around lines 90 - 139, The rollback test coverage in
TriangulationRollbackTransaction is still limited to 2D and 3D; extend the
existing tests to also run for 4D and 5D. Rework the tests around the
dimension-generic TriangulationRollbackTransaction and Triangulation types so
the same assertions are exercised across D = 2..5, ideally by introducing a
small dimension macro or equivalent shared helper to keep the test file concise.

Source: Coding guidelines

src/core/repair.rs (1)

1346-1354: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Skip the rollback snapshot when there is nothing to repair.

repair_local_facet_issues now snapshots the full TDS before repair_local_facet_issues_with_frontier discovers an empty issues map and returns 0. Add an early return to avoid cloning the whole triangulation for the documented no-op path.

♻️ Proposed fix
 pub fn repair_local_facet_issues(
     &mut self,
     issues: &FacetIssuesMap,
     max_simplices_removed: usize,
 ) -> Result<usize, InsertionError> {
+    if issues.is_empty() {
+        return Ok(0);
+    }
+
     let mut transaction = TriangulationRollbackTransaction::begin(self);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/repair.rs` around lines 1346 - 1354, The no-op path in
repair_local_facet_issues still starts a TriangulationRollbackTransaction and
snapshots the full triangulation even when there are no facet issues to repair.
Add an early return at the start of repair_local_facet_issues, before creating
TriangulationRollbackTransaction::begin(self), so an empty issues map returns 0
immediately and avoids cloning the TDS; use repair_local_facet_issues and
repair_local_facet_issues_with_frontier as the key locations.
src/delaunay/insertion.rs (1)

86-99: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Gate rollback snapshots on the actual repair schedule.

This currently starts a Delaunay rollback transaction whenever delaunay_repair_policy != Never, even when maybe_repair_after_insertion_capped would skip repair for the next insertion. Because DelaunayRollbackTransaction::begin snapshots the TDS, cadenced repair policies can pay a full-clone cost on insertions that cannot run post-repair/check work.

♻️ Proposed fix
 fn post_insertion_transaction_required(&self) -> bool {
     let next_insertion_count = self
         .insertion_state
         .delaunay_repair_insertion_count
         .saturating_add(1);
     let could_have_simplices_after_insertion = self.tri.tds.number_of_simplices() > 0
         || self.tri.tds.number_of_vertices().saturating_add(1) > D;
-    could_have_simplices_after_insertion
-        && (self.insertion_state.delaunay_repair_policy != DelaunayRepairPolicy::Never
-            || self
-                .insertion_state
-                .delaunay_check_policy
-                .should_check(next_insertion_count))
+    if !could_have_simplices_after_insertion {
+        return false;
+    }
+
+    self.should_run_delaunay_repair_for(self.tri.topology_guarantee(), next_insertion_count)
+        || self
+            .insertion_state
+            .delaunay_check_policy
+            .should_check(next_insertion_count)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/delaunay/insertion.rs` around lines 86 - 99, Gate the rollback snapshot
in post_insertion_transaction_required on the actual repair/check schedule
rather than only on DelaunayRepairPolicy::Never. Update the logic in
post_insertion_transaction_required so it mirrors the same cadence used by
maybe_repair_after_insertion_capped, and only returns true when a post-insertion
repair or check can actually run; keep DelaunayRollbackTransaction::begin
untouched and use insertion_state.delaunay_repair_insertion_count plus the
repair/check policy symbols to locate the change.
src/delaunay/rollback.rs (1)

143-204: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Cover all transaction close paths and feasible dimensions.

The new guard has three distinct close paths (commit, explicit rollback, and drop rollback), but these tests only exercise drop rollback in 2D. Please add focused coverage for committed mutations and explicit rollback, and extend the invariant checks through D=3–5 where feasible. As per coding guidelines, “Tests should verify mathematical, geometric, and topological invariants; dimension-generic tests should cover 2D through 5D whenever feasible.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/delaunay/rollback.rs` around lines 143 - 204, The current rollback tests
only cover the drop-based close path in 2D, so expand coverage in the same
rollback test module around DelaunayRollbackTransaction to exercise all three
close paths: commit, explicit rollback, and drop rollback. Add focused
assertions for committed mutations and explicit rollback behavior using the
existing helpers like DelaunayRollbackTransaction::begin,
insert_uncommitted_vertex, and rollback/commit flows. Also generalize or
duplicate the invariant checks across feasible dimensions, extending the same
restore/invalidate expectations through 3D, 4D, and 5D where the triangulation
test fixtures support it.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/dev/rust.md`:
- Around line 246-247: Update the wording in the Rust docs so the phrase in the
“Higher level” section is hyphenated as a compound modifier, i.e. use
“Higher-level” in the DelaunayTriangulation guidance. Locate the text around the
DelaunayTriangulation rollback guard note and adjust only that heading/label
wording without changing the rest of the sentence.

---

Nitpick comments:
In `@src/core/repair.rs`:
- Around line 1346-1354: The no-op path in repair_local_facet_issues still
starts a TriangulationRollbackTransaction and snapshots the full triangulation
even when there are no facet issues to repair. Add an early return at the start
of repair_local_facet_issues, before creating
TriangulationRollbackTransaction::begin(self), so an empty issues map returns 0
immediately and avoids cloning the TDS; use repair_local_facet_issues and
repair_local_facet_issues_with_frontier as the key locations.

In `@src/core/rollback.rs`:
- Around line 90-139: The rollback test coverage in
TriangulationRollbackTransaction is still limited to 2D and 3D; extend the
existing tests to also run for 4D and 5D. Rework the tests around the
dimension-generic TriangulationRollbackTransaction and Triangulation types so
the same assertions are exercised across D = 2..5, ideally by introducing a
small dimension macro or equivalent shared helper to keep the test file concise.

In `@src/delaunay/insertion.rs`:
- Around line 86-99: Gate the rollback snapshot in
post_insertion_transaction_required on the actual repair/check schedule rather
than only on DelaunayRepairPolicy::Never. Update the logic in
post_insertion_transaction_required so it mirrors the same cadence used by
maybe_repair_after_insertion_capped, and only returns true when a post-insertion
repair or check can actually run; keep DelaunayRollbackTransaction::begin
untouched and use insertion_state.delaunay_repair_insertion_count plus the
repair/check policy symbols to locate the change.

In `@src/delaunay/rollback.rs`:
- Around line 143-204: The current rollback tests only cover the drop-based
close path in 2D, so expand coverage in the same rollback test module around
DelaunayRollbackTransaction to exercise all three close paths: commit, explicit
rollback, and drop rollback. Add focused assertions for committed mutations and
explicit rollback behavior using the existing helpers like
DelaunayRollbackTransaction::begin, insert_uncommitted_vertex, and
rollback/commit flows. Also generalize or duplicate the invariant checks across
feasible dimensions, extending the same restore/invalidate expectations through
3D, 4D, and 5D where the triangulation test fixtures support it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 3e738d72-aaf6-43e8-af9e-cb58266fe3db

📥 Commits

Reviewing files that changed from the base of the PR and between 60e99d1 and 8803872.

📒 Files selected for processing (11)
  • docs/dev/rust.md
  • src/core/algorithms/flips.rs
  • src/core/insertion.rs
  • src/core/repair.rs
  • src/core/rollback.rs
  • src/core/tds/rollback.rs
  • src/core/tds/storage.rs
  • src/delaunay/deletion.rs
  • src/delaunay/insertion.rs
  • src/delaunay/rollback.rs
  • src/lib.rs

Comment thread docs/dev/rust.md Outdated
@codecov

codecov Bot commented Jun 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.95062% with 49 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.70%. Comparing base (60e99d1) to head (c303f44).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/core/algorithms/flips.rs 73.61% 19 Missing ⚠️
src/core/repair.rs 86.52% 19 Missing ⚠️
src/delaunay/deletion.rs 85.24% 9 Missing ⚠️
src/delaunay/insertion.rs 98.27% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #480      +/-   ##
==========================================
+ Coverage   91.66%   91.70%   +0.04%     
==========================================
  Files          81       84       +3     
  Lines       73358    73902     +544     
==========================================
+ Hits        67240    67769     +529     
- Misses       6118     6133      +15     
Flag Coverage Δ
unittests 91.70% <93.95%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

- Share owner-bound TDS rollback guards across TDS, Triangulation, and Delaunay wrappers.
- Restore insertion bookkeeping with rollback and restore or invalidate spatial indexes according to the mutation policy.
- Avoid rollback snapshots when local facet repair has no issues or post-insertion repair/check cadence cannot run.
@acgetchell acgetchell enabled auto-merge (squash) June 26, 2026 07:13
@acgetchell acgetchell merged commit 8ef6a95 into main Jun 26, 2026
22 checks passed
@acgetchell acgetchell deleted the refactor/364-rollback-primitives branch June 26, 2026 07:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api bug Something isn't working rust Pull requests that update rust code topology

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant