quickwit-oss/tantivy · error

Mmap cache lock is poisoned.

Error message

Mmap cache lock is poisoned.

What it means

This panic occurs when the RwLock guarding the segment mmap cache is poisoned, i.e. another thread panicked while holding the read or write lock on that cache. Tantivy uses expect() here because lock poisoning on an internal cache means a prior thread crashed mid-operation, leaving the cache in an indeterminate state.

Source

Thrown at src/directory/mmap_directory/mod.rs:301

    fn resolve_path(&self, relative_path: &Path) -> PathBuf {
        self.inner.root_path.join(relative_path)
    }

    /// Returns some statistical information
    /// about the Mmap cache.
    ///
    /// The `MmapDirectory` embeds a `MmapDirectory`
    /// to avoid multiplying the `mmap` system calls.
    pub fn get_cache_info(&self) -> CacheInfo {
        self.inner
            .mmap_cache
            .write()
            .expect("mmap cache lock is poisoned")
            .remove_weak_ref();
        self.inner
            .mmap_cache
            .read()
            .expect("Mmap cache lock is poisoned.")
            .get_info()
    }
}

/// We rely on fs2 for file locking. On Windows & MacOS this
/// uses BSD locks (`flock`). The lock is actually released when
/// the `File` object is dropped and its associated file descriptor
/// is closed.
struct ReleaseLockFile {
    _file: File,
    path: PathBuf,
}

impl Drop for ReleaseLockFile {
    fn drop(&mut self) {
        debug!("Releasing lock {:?}", self.path);
    }
}

View on GitHub (pinned to b5d8deb80c)

Solutions

  1. Find and fix the original panic that poisoned the lock (scan earlier logs for the root panic/backtrace).
  2. Reinitialize the index reader/searcher after the panic instead of reusing the same Index across threads.
  3. Ensure custom Directory/wrap implementations do not panic while accessing mmap cache internals.
  4. If caused by memory pressure, reduce concurrent mmap usage or increase address space / mmap limits.

Example fix

// before: reusing a shared Index across threads after a panic
let info = index.get_cache_info(); // panics: poisoned
// after: recreate the index handle after catching the root failure
let index = Index::open_in_dir(&dir)?;
let info = index.get_cache_info();
Defensive patterns

Strategy: try-catch

Validate before calling

// Rust: check index dir is readable/mmap-able before use
std::fs::read(dir.join("meta.json")).map_err(|e| format!("index unreadable: {e}"))?;

Type guard

fn lock_ok<T>(guard: std::sync::LockResult<T>) -> Option<T> { guard.ok() }

Try / catch

let info = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| index.get_cache_info()));
match info {
    Ok(v) => use(v),
    Err(_) => { let index = reopen_index(&dir)?; }
}

Prevention

When it happens

Trigger: A panic happens in another thread while it holds the mmap cache lock (e.g. during cache eviction or weak-ref removal), then any thread calls get_cache_info().

Common situations: A background indexing or search thread panics (e.g. corrupt segment, OOM during mmap) while holding the cache lock; the panic message seen is the propagated poisoning, not the root cause.

Related errors


AI-assisted analysis of quickwit-oss/tantivy@b5d8deb80c (2026-09-05). Data as JSON: /api/errors/bf2d79c324e65107. Report an issue: GitHub.