Skip to content

fix(index): resolve frag-reuse index single-flight self-deadlock in open_frag_reuse_index - #42

Open
zehiko wants to merge 2 commits into
release-9.0.0from
zehiko/fix-fri-single-flight-deadlock-r9
Open

fix(index): resolve frag-reuse index single-flight self-deadlock in open_frag_reuse_index#42
zehiko wants to merge 2 commits into
release-9.0.0from
zehiko/fix-fri-single-flight-deadlock-r9

Conversation

@zehiko

@zehiko zehiko commented Aug 18, 2026

Copy link
Copy Markdown
Member

Port of #41 (release-8.0.0) to release-9.0.0.

Mechanism

DatasetIndexInternalExt::open_frag_reuse_index passes a loader closure to index_cache.get_or_insert_with_key(frag_reuse_key, ...) that calls self.load_index(&frag_reuse_uuid). load_index runs load_indices(), which does its own get_or_insert_with_key on the same frag_reuse/{uuid} cache key. moka's optionally_get_with holds the waiter's write lock across the init future, so the re-entrant inner call parks on read().await against its own outer write guard: a permanent, zero-CPU self-deadlock. Every subsequent reader of the dataset then parks behind the poisoned key — all reads of the dataset stall indefinitely.

Trigger conditions

  • The frag_reuse/{uuid} entry must be absent at the second get_or_insert_with_key — i.e. evicted in the window between the pre-populate in load_indices() and the probe in open_frag_reuse_index.
  • In practice: a huge materialized FRI (large weight → prime eviction target) + FRI uuid churn from deferred-remap compactions + concurrent readers keeping the cache under pressure.

Impact

Root cause of the ab-repro-0155 total registration stall on 2026-08-18, and of RR-5448's previously unexplained "second stall mechanism" on ab-repro-new.

Fix

The loader now uses the IndexMetadata already fetched by load_index_by_name at the top of the function and calls load_frag_reuse_index_details + open_frag_reuse_index directly — mirroring the (safe) loader inside load_indices(). Guard comments added on both FRI loader closures: they must never call load_indices/load_index, which re-enter the same cache key.

Verification

  • Deterministic regression test (rust/lance/tests/frag_reuse_deadlock.rs): a zero-capacity index cache forces the poisoned loader path; unfixed = deadlock caught by a 20s tokio timeout, fixed = passes.
  • Live verification on ab-repro-0155: the unfixed binary wedges in ~8 min under 80k-registration stress; the fixed binary passed 67 FRI evictions / 14 compactions in 4 min with zero stuck cache waits.

Upstream

Upstream lancedb/lance main carries the same defect (rust/lance/src/index.rs:3003-3005, loader calls self.load_index(...)), as does our release-9.0.0 branch (rust/lance/src/index.rs:2494-2496) — this fix should be forward-ported.

🤖 Generated with Claude Code

zehiko and others added 2 commits August 18, 2026 16:35
…pen_frag_reuse_index

The cache loader passed to index_cache.get_or_insert_with_key on the
frag_reuse/{uuid} key called self.load_index(uuid), which re-enters
load_indices() and performs get_or_insert_with_key on the SAME key.
moka holds the waiter's write lock across the init future, so the
re-entrant call parks against its own guard: a permanent zero-CPU
deadlock that stalls every reader of the dataset.

Use the IndexMetadata already fetched by load_index_by_name instead,
mirroring the safe loader in load_indices(). Guard comments added on
both FRI loader closures. Deterministic regression test included
(zero-capacity index cache forces the poisoned loader path).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…order

The lance clippy CI job denies print_stdout even in tests, and rustfmt
wants lance_index::IndexType sorted before the lance_index submodule
imports. The test's assertions and panic messages carry the signal;
the progress prints added nothing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@emilk
emilk requested a review from amunra August 21, 2026 08:36
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.

1 participant