Skip to content

fix(mem-wal): expose typed writer fence detection#7896

Open
beinan wants to merge 3 commits into
lance-format:mainfrom
beinan:fix/mem-wal-typed-fence-error
Open

fix(mem-wal): expose typed writer fence detection#7896
beinan wants to merge 3 commits into
lance-format:mainfrom
beinan:fix/mem-wal-typed-fence-error

Conversation

@beinan

@beinan beinan commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add Error::is_mem_wal_fenced() for detecting peer epoch takeovers without parsing error text
  • preserve typed fence reasons through WAL and MemTable completion channels
  • distinguish confirmed epoch conflicts from ambiguous lost acknowledgements
  • preserve the public DurabilityResult::Failed(String) API
  • update MemWAL regressions for put, replay/open, force_seal_active, wait_for_flush_drain, and close

Testing

  • cargo test -p lance-core --doc is_mem_wal_fenced
  • cargo test -p lance-core fence
  • cargo test -p lance fence
  • cargo test -p lance dataset::mem_wal::manifest::tests::test_claim_epoch_lost_ack_is_not_peer_fence -- --exact
  • cargo test -p lance dataset::mem_wal::write::tests::test_close_propagates_frozen_memtable_flush_failure -- --exact
  • cargo test -p lance dataset::mem_wal::write::tests::test_frozen_retained_after_failed_flush -- --exact
  • cargo fmt --all -- --check
  • cargo clippy --all --tests --benches -- -D warnings

@github-actions github-actions Bot added the bug Something isn't working label Jul 21, 2026
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

MemWAL peer-epoch fencing now uses source-aware typed errors across manifest conflict handling, error cloning, memtable flush completion, writer shutdown, and regression tests. Ambiguous manifest write outcomes remain non-fencing when the write may have committed.

Changes

MemWAL fencing

Layer / File(s) Summary
Typed fencing error contract
rust/lance-core/src/error.rs, rust/lance/src/dataset/mem_wal/wal.rs
Adds source-aware fence classification and preserves fence reasons during cloning and flush-failure conversion.
Manifest conflict classification
rust/lance/src/dataset/mem_wal/manifest.rs, rust/lance/src/dataset/mem_wal/test_util.rs
Preserves object-store sources, detects confirmed version conflicts, distinguishes peer fencing from lost acknowledgements, and adds manifest failure injection controls.
Typed flush completion propagation
rust/lance/src/dataset/mem_wal/memtable.rs, rust/lance/src/dataset/mem_wal/write.rs
Adds typed memtable completion watchers and propagates WalFlushFailure through flush draining and writer close paths.
WAL regression coverage
rust/lance/src/dataset/mem_wal/wal.rs, rust/lance/src/dataset/mem_wal/write.rs
Updates fencing assertions across watcher, replay, durable put, close, and flush-drain scenarios.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Possibly related PRs

Suggested reviewers: hamersaw, xuanwo

Sequence Diagram(s)

sequenceDiagram
  participant ObjectStore
  participant Manifest
  participant MemTableFlushHandler
  participant ShardWriter
  ObjectStore-->>Manifest: return sourced manifest write error
  Manifest->>Manifest: detect confirmed version conflict
  Manifest-->>ShardWriter: fenced_by_peer or may-have-committed error
  MemTableFlushHandler->>ShardWriter: signal typed WalFlushFailure
  ShardWriter->>ShardWriter: drain typed completion watcher
  ShardWriter-->>ShardWriter: return is_mem_wal_fenced() result
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: exposing typed MemWAL writer fence detection.
Description check ✅ Passed The description is directly related to the MemWAL fence-detection and typed completion changes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.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 unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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 `@rust/lance-core/src/error.rs`:
- Around line 987-992: Preserve nested fence reasons in both propagation paths:
in rust/lance-core/src/error.rs lines 987-992, update the Error::Fenced cloning
logic to recover FenceReason from the wrapped source before falling back to
string-only cloning; in rust/lance/src/dataset/mem_wal/write.rs lines 630-644,
make WalFlushFailure extraction inspect error sources and add round-trip tests
for wrapped peer-claimed-epoch and persistence-failure fences.
- Around line 489-501: Add a Rustdoc # Examples section to the public
Error::is_mem_wal_fenced method, using a compiling example that demonstrates
true for peer epoch takeovers and false for persistence failures. Include
documentation links to Error::fenced_by_peer, Error::writer_poisoned, and
Error::fence_reason, keeping the example aligned with the documented
peer/persistence distinction.

In `@rust/lance/src/dataset/mem_wal/manifest.rs`:
- Around line 485-488: Update the shard-claim error handling around the
conditional manifest write so a reread showing latest_epoch equal to next_epoch
is not treated as peer fencing after an ambiguous or lost response. Distinguish
confirmed conditional conflicts from ambiguous I/O, or validate ownership with a
unique claim token before returning Error::fenced_by_peer; add a regression test
covering a committed write whose response is lost.

In `@rust/lance/src/dataset/mem_wal/write.rs`:
- Around line 611-614: Preserve the existing public `DurabilityResult::Failed`
payload type so downstream constructors and pattern matches remain compatible.
Revert the variant change from `String` to `WalFlushFailure`, or introduce an
explicit backward-compatible migration path before exposing the typed failure
carrier.
🪄 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.yaml

Review profile: QUIET

Plan: Pro Plus

Run ID: d761c4ac-2873-4755-a047-641e5f3b0d06

📥 Commits

Reviewing files that changed from the base of the PR and between 4696387 and 3e0cffc.

📒 Files selected for processing (4)
  • rust/lance-core/src/error.rs
  • rust/lance/src/dataset/mem_wal/manifest.rs
  • rust/lance/src/dataset/mem_wal/wal.rs
  • rust/lance/src/dataset/mem_wal/write.rs

Comment thread rust/lance-core/src/error.rs
Comment thread rust/lance-core/src/error.rs
Comment thread rust/lance/src/dataset/mem_wal/manifest.rs
Comment thread rust/lance/src/dataset/mem_wal/write.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
rust/lance/src/dataset/mem_wal/memtable.rs (1)

343-368: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Make completion-cell lifecycle failures explicit.

Line 348 panics when the cell is absent, while Lines 364-368 silently drop a duplicate/missing completion. Return a descriptive Result from reader creation and at least debug_assert! on an unexpected second signal.

As per coding guidelines, “Never use .unwrap(), .expect(), panic!(), or assert!() in library code for fallible operations” and “Do not silently guard against impossible conditions.”

🤖 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 `@rust/lance/src/dataset/mem_wal/memtable.rs` around lines 343 - 368, Update
create_typed_memtable_flush_completion to return a descriptive Result instead of
unwrapping and expecting the completion cell, propagating lock or missing-cell
failures without panicking. In signal_memtable_flush_complete and
signal_typed_memtable_flush_complete, retain one-shot completion behavior but
add debug_assert! checks for an absent cell so duplicate or missing signals are
no longer silently ignored.

Source: Coding guidelines

🧹 Nitpick comments (1)
rust/lance-core/src/error.rs (1)

1062-1077: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use rstest cases for these parameterized fence tests

  • rust/lance-core/src/error.rs#L1062-L1077
  • rust/lance/src/dataset/mem_wal/wal.rs#L1737-L1757

Replace the manual for loops with named #[case::...] tests so these fence cases follow the repo’s parameterized-test convention.

🤖 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 `@rust/lance-core/src/error.rs` around lines 1062 - 1077, Replace the manual
parameter loop in cloneable_error_preserves_nested_fence_reason in
rust/lance-core/src/error.rs:1062-1077 with an rstest parameterized test using
descriptive named cases for each fence reason. Apply the same conversion to the
corresponding fence test in rust/lance/src/dataset/mem_wal/wal.rs:1737-1757,
preserving each case’s inputs and assertions.

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 `@rust/lance/src/dataset/mem_wal/test_util.rs`:
- Around line 60-65: Document the public methods fail_manifest_puts and
set_manifest_lost_ack with Rustdoc describing their manifest-failure controls,
including examples showing configuration usage. Add links to
failing_memory_store and FailControls, following the repository’s public API
documentation conventions.

---

Outside diff comments:
In `@rust/lance/src/dataset/mem_wal/memtable.rs`:
- Around line 343-368: Update create_typed_memtable_flush_completion to return a
descriptive Result instead of unwrapping and expecting the completion cell,
propagating lock or missing-cell failures without panicking. In
signal_memtable_flush_complete and signal_typed_memtable_flush_complete, retain
one-shot completion behavior but add debug_assert! checks for an absent cell so
duplicate or missing signals are no longer silently ignored.

---

Nitpick comments:
In `@rust/lance-core/src/error.rs`:
- Around line 1062-1077: Replace the manual parameter loop in
cloneable_error_preserves_nested_fence_reason in
rust/lance-core/src/error.rs:1062-1077 with an rstest parameterized test using
descriptive named cases for each fence reason. Apply the same conversion to the
corresponding fence test in rust/lance/src/dataset/mem_wal/wal.rs:1737-1757,
preserving each case’s inputs and assertions.
🪄 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.yaml

Review profile: QUIET

Plan: Pro Plus

Run ID: e9546022-6755-4c7e-828b-e8b5e0bdef1a

📥 Commits

Reviewing files that changed from the base of the PR and between 3e0cffc and daef919.

📒 Files selected for processing (6)
  • rust/lance-core/src/error.rs
  • rust/lance/src/dataset/mem_wal/manifest.rs
  • rust/lance/src/dataset/mem_wal/memtable.rs
  • rust/lance/src/dataset/mem_wal/test_util.rs
  • rust/lance/src/dataset/mem_wal/wal.rs
  • rust/lance/src/dataset/mem_wal/write.rs

Comment thread rust/lance/src/dataset/mem_wal/test_util.rs Outdated
@hamersaw hamersaw self-assigned this Jul 21, 2026
@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.67901% with 7 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
rust/lance-core/src/error.rs 91.48% 4 Missing ⚠️
rust/lance/src/dataset/mem_wal/manifest.rs 94.59% 2 Missing ⚠️
rust/lance/src/dataset/mem_wal/memtable.rs 93.33% 0 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@hamersaw
hamersaw self-requested a review July 22, 2026 00:59
@beinan

beinan commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the follow-up review and patch coverage report in e6d89a6fc:

  • create_typed_memtable_flush_completion now returns a descriptive Result for missing/poisoned lifecycle state
  • duplicate public and typed completion signals now trigger debug_assert!, with lifecycle unit tests
  • direct and nested peer/persistence clone paths are covered using named rstest cases
  • wrapped WAL fence tests now use named rstest cases
  • manifest conflict and ordinary-I/O classification branches have dedicated coverage

Validated with targeted unit tests, fence regressions, close/flush-drain regressions, Rustdoc, fmt, and cargo clippy --all --tests --benches -- -D warnings.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@rust/lance/src/dataset/mem_wal/memtable.rs`:
- Around line 359-376: Update signal_memtable_flush_complete and
signal_typed_memtable_flush_complete to recover poisoned completion mutex guards
via PoisonError::into_inner() instead of unwrap, then always publish the
terminal result so drain watchers and close do not remain pending. Add a
regression test that creates the watcher before poisoning the mutex and verifies
flush completion is still signaled.
🪄 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.yaml

Review profile: QUIET

Plan: Pro Plus

Run ID: 86649667-2083-4a7a-999e-6910bc47fd4c

📥 Commits

Reviewing files that changed from the base of the PR and between daef919 and e6d89a6.

📒 Files selected for processing (6)
  • rust/lance-core/src/error.rs
  • rust/lance/src/dataset/mem_wal/manifest.rs
  • rust/lance/src/dataset/mem_wal/memtable.rs
  • rust/lance/src/dataset/mem_wal/test_util.rs
  • rust/lance/src/dataset/mem_wal/wal.rs
  • rust/lance/src/dataset/mem_wal/write.rs

Comment on lines +359 to 376
let cell = self.memtable_flush_completion.lock().unwrap().take();
if let Some(cell) = cell {
cell.write(result);
} else {
debug_assert!(false, "memtable flush completion signaled more than once");
}
}

pub(crate) fn signal_typed_memtable_flush_complete(&self, result: MemTableFlushResult) {
let cell = self.typed_memtable_flush_completion.lock().unwrap().take();
if let Some(cell) = cell {
cell.write(result);
} else {
debug_assert!(
false,
"typed memtable flush completion signaled more than once"
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Recover poisoned completion mutexes before signaling.

Both .lock().unwrap() calls panic on a poisoned mutex. Since MemTableFlushHandler::flush_memtable publishes these results before removing drain watchers, that panic leaves wait_for_flush_drain() and close() awaiting an unresolved watcher indefinitely. Recover the guard with PoisonError::into_inner() and publish a terminal result; add a watcher-created-before-poisoning regression test.

As per coding guidelines, “Never use .unwrap(), .expect(), panic!(), or assert!() in library code for fallible operations.”

🤖 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 `@rust/lance/src/dataset/mem_wal/memtable.rs` around lines 359 - 376, Update
signal_memtable_flush_complete and signal_typed_memtable_flush_complete to
recover poisoned completion mutex guards via PoisonError::into_inner() instead
of unwrap, then always publish the terminal result so drain watchers and close
do not remain pending. Add a regression test that creates the watcher before
poisoning the mutex and verifies flush completion is still signaled.

Source: Coding guidelines

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

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants