Skip to content

chore: Define and use impl From<PoisonError> for Error - #3312

Open
scovich wants to merge 1 commit into
delta-io:mainfrom
scovich:mutex-poison-error
Open

chore: Define and use impl From<PoisonError> for Error#3312
scovich wants to merge 1 commit into
delta-io:mainfrom
scovich:mutex-poison-error

Conversation

@scovich

@scovich scovich commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

What changes are proposed in this pull request?

As per title.

The upshot is that code like this:

let value = self.lock().map_error(|_| Error::internal_error(...))?;

Can now just use ? normally:

let value = self.lock()?;

This saves a surprising number of LoC across the various callsites, because fmt likes to wrap the map_error calls across several lines.

The main possible controversy of this change is that all error messages become the generic "poisoned mutex", and FFI no longer surfaces poisoned mutex errors as Error::Generic. The former seems fine, because Error::Internal anyway grabs a backtrace. The latter is arguably a Good Thing because the generic bucket is horribly over-used.

How was this change tested?

Existing unit tests.

@codecov

codecov Bot commented Sep 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 45.45455% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.33%. Comparing base (14d6d8c) to head (9549692).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
ffi/src/ffi_tracing.rs 0.00% 3 Missing and 1 partial ⚠️
ffi/src/incremental_scan.rs 50.00% 0 Missing and 2 partials ⚠️
ffi/src/scan.rs 0.00% 0 Missing and 2 partials ⚠️
kernel/src/metrics/metered_storage.rs 0.00% 0 Missing and 2 partials ⚠️
ffi/src/commit_range.rs 0.00% 0 Missing and 1 partial ⚠️
ffi/src/table_changes.rs 0.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3312      +/-   ##
==========================================
+ Coverage   90.30%   90.33%   +0.03%     
==========================================
  Files         251      250       -1     
  Lines       89039    89036       -3     
  Branches    89039    89036       -3     
==========================================
+ Hits        80404    80432      +28     
+ Misses       5704     5668      -36     
- Partials     2931     2936       +5     

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions github-actions 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.

AI Review (draft - human review required)

Show review

This is a clean, well-scoped refactor. The new impl<T> From<std::sync::PoisonError<T>> for Error is sound: it is generic only over the guarded type, matches the concrete PoisonError, and sits alongside the other From impls that exist to make ? work across the crate. There is no coherence or overlap risk, and it transparently covers RwLock poisoning too. Moving poisoned-lock failures out of the over-used Generic bucket into Internal is a reasonable classification. The test-side .unwrap()/.expect() to ? conversions all sit in DeltaResult-returning contexts and preserve assertion strength; the untouched .lock().unwrap() at scan/tests.rs:1698 was correctly left alone because it returns a plain Vec.

No blocking issues.

Non-blocking notes

Nit1. kernel/src/error.rs:548 (the new From<PoisonError<T>> impl). Because both Error and PoisonError are public, this impl is permanent public surface and makes "poison is always Internal" an ambient crate-wide decision that call sites can no longer override without giving up ?. That looks like the intended tradeoff, but the adjacent From<Infallible> impl carries a comment explaining why the conversion exists; a one-line comment here recording the intentional invariant would match that precedent.
Raised by: architecture-reviewer
Suggested fix: add a short doc comment above the impl noting that lock poisoning is deliberately surfaced as Error::Internal crate-wide.

Nit2. kernel/src/error.rs:550. Collapsing every site to the message "poisoned mutex" drops the per-callsite context (for example "poisoned scan-metadata iterator mutex"). internal_error calls Backtrace::capture(), which only yields a usable trace when backtraces are enabled, so the PR description's claim that Internal "anyway grabs a backtrace" does not hold in a default build where the specific site is then unrecoverable. This is a diagnosability tradeoff, not a correctness problem.
Raised by: maintainer-codex-reviewer, maintainer-claude-reviewer
Suggested fix: none required; consider softening the PR description wording about backtraces.

Nit3. ffi/src/scan.rs:498. lock_iter() is now a trivial Ok(self.data.lock()?) wrapper, yet scan_metadata_next_arrow_impl inlines data.data.lock()? while scan_metadata_next still calls lock_iter(). The helper carries the doc comment about concurrent-next blocking, so the two callsites reading differently is slightly confusing.
Raised by: maintainer-codex-reviewer, maintainer-claude-reviewer
Suggested fix: route both callsites through lock_iter(), or drop the helper and inline both.

Summary
A behavior-preserving cleanup that removes repetitive lock-error plumbing via a single, correct From<PoisonError> conversion. No blocking issues. The FFI error-kind change from Generic to Internal is real and observable to C consumers, but it is intentional and already disclosed in the PR description, and no existing FFI test depends on the old kind. Remaining points are minor.


Automated review - workflow run

Comment thread kernel/src/error.rs
}
}

impl<T> From<std::sync::PoisonError<T>> for Error {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nit1 This From<PoisonError> impl is permanent public surface (both Error and PoisonError are public), making "poison is always Internal" a crate-wide decision no call site can override without giving up ?. Looks intended, but the adjacent From impl documents why it exists; a one-line comment here would record this invariant as deliberate. Raised by: architecture-reviewer. Suggested fix: add a short doc comment noting lock poisoning is intentionally surfaced as Error::Internal crate-wide.

Comment thread kernel/src/error.rs

impl<T> From<std::sync::PoisonError<T>> for Error {
fn from(_error: std::sync::PoisonError<T>) -> Self {
Self::internal_error("poisoned mutex")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nit2 Collapsing all sites to "poisoned mutex" drops per-callsite context. internal_error uses Backtrace::capture(), which only yields a trace when backtraces are enabled, so the PR description's claim that Internal "anyway grabs a backtrace" does not hold in a default build and the specific site becomes unrecoverable. Diagnosability tradeoff, not a correctness issue. Raised by: maintainer-codex-reviewer, maintainer-claude-reviewer. Suggested fix: none required; consider softening the PR description wording.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

flyby: ^ I'm cool with the PoisonError conversion so long as backtraces are captured 👍

Comment thread ffi/src/scan.rs
self.data
.lock()
.map_err(|_| Error::generic("poisoned scan-metadata iterator mutex"))
Ok(self.data.lock()?)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nit3 lock_iter() is now a trivial Ok(self.data.lock()?) wrapper, but scan_metadata_next_arrow_impl inlines data.data.lock()? while scan_metadata_next still calls lock_iter(). The helper carries the concurrency doc comment, so the split usage is slightly confusing. Raised by: maintainer-codex-reviewer, maintainer-claude-reviewer. Suggested fix: route both callsites through lock_iter(), or drop the helper and inline both.

@github-actions

Copy link
Copy Markdown

Benchmark results: ✅ Pass

Summary: 🚀 0  ·  ✅ 8  ·  ☑️ 3  ·  🚧 4  ·  ❌ 0

Per-benchmark results (15 rows)
Test Change Base PR
clustered/readMetadataLatestPredicate/serial ✅ 1.00x 106.9±3.56ms 106.5±3.84ms
crcLatest/snapshotLatest ✅ 1.03x faster 10.9±0.34ms 10.6±0.39ms
crcMissing/snapshotLatest ☑️ 1.02x slower 25.7±0.67ms 26.3±0.70ms
crcSlightlyStale/snapshotLatest ✅ 1.02x faster 11.8±0.28ms 11.6±0.26ms
crcVeryStale/snapshotLatest 🚧 1.04x slower 17.3±0.37ms 18.0±0.46ms
partitioned/readMetadataLatestPredicate/serial ☑️ 1.03x slower 61.6±2.31ms 63.4±4.00ms
v1Checkpoint/readMetadataLatest/serial 🚧 1.04x slower 13.2±0.22ms 13.7±0.42ms
v1Checkpoint/snapshotLatest 🚧 1.04x slower 841.2±30.19µs 876.9±36.63µs
v2Checkpoint/readMetadataLatest/parallel2 ✅ 1.01x faster 9.6±0.47ms 9.5±0.55ms
v2Checkpoint/readMetadataLatest/serial ✅ 1.01x faster 15.7±0.28ms 15.5±0.31ms
v2Checkpoint/snapshotLatest 🚧 1.04x slower 853.0±38.14µs 887.4±32.47µs
wideSchemaJsonStats/readMetadataLatestPredicate/serial ✅ 1.00x 78.1±1.19ms 78.4±1.89ms
wideSchemaJsonStats/snapshotLatest ✅ 1.00x 2.4±0.05ms 2.4±0.05ms
wideSchemaStructStats/readMetadataLatestPredicate/serial ☑️ 1.01x slower 34.2±0.71ms 34.6±0.63ms
wideSchemaStructStats/snapshotLatest ✅ 1.00x 2.3±0.04ms 2.3±0.02ms

Legend: 🚀 ≥1.15x faster  · ✅ faster or unchanged  · ☑️ ≤1.03x slower  · 🚧 1.03x-1.15x slower  · ❌ ≥1.15x slower
Commit: 9549692 · Trigger: auto-push · Tags: base · Updated: 2026-09-11 08:25 PDT

@dengsh12 dengsh12 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM thanks

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.

3 participants