dmtrKovalenko / dmtrKovalenko/fff
SIGSEGV crash: LMDB reader slot leak causes MDB_READERS_FULL after multiple nvim sessions
- Dominant language
- Rust
- Stars
- 10.7k
- Forks
- 446
- Avg merge
- 1d 21h
- Merged PRs (30d)
- 39
Description
## Summary
fff.nvim's Rust extension segfaults with `SIGSEGV` when opening the file picker, caused by LMDB reader slot exhaustion (`MDB_READERS_FULL`).
## Crash Banner
```
fff.nvim's rust extension hit a segfault and is about to die.
Please file the bug at https://github.com/dmtrKovalenko/fff/issues with this banner attached.
=== CRASH END SIGSEGV ===
```
## Root Cause
fff.nvim leaks LMDB reader slots. Each Neovim session that loads fff.nvim occupies ~12-18 reader slots in the LMDB lock file (`lock.mdb`) but never releases them. The default `maxreaders` limit is 126, so after ~7-10 long-running nvim sessions, the reader table is exhausted.
When a new nvim session tries to open fff's file picker, LMDB returns `MDB_READERS_FULL`, which is not handled gracefully and leads to a segfault.
### Log Evidence
From the fff log file (`~/.local/state/nvim/fff+*.log`):
```
2026-07-09T04:06:34.742389Z ERROR ThreadId(02) fff_nvim::error: string_value="Failed to start read transaction for frecency database: MDB_READERS_FULL: Environment maxreaders limit reached"
=== CRASH SIGSEGV (fff) ===
fff.nvim's rust extension hit a segfault and is about to die.
Please file the bug at https://github.com/dmtrKovalenko/fff/issues with this banner attached.
=== CRASH END SIGSEGV ===
```
### Reader Slot Analysis
Parsing the LMDB lock file (`~/.cache/nvim/fff_nvim/lock.mdb`) shows all 126 reader slots occupied:
```
Max readers: 126
Used slots: 126 (alive=115, dead=11)
Free slots: 0
```
7 long-running nvim processes (running for 1-3 days) each leaked 12-18 reader slots:
| PID | Slots Leaked | Process Age |
|-----------|-------------|-------------|
| 1911588 | 16 | ~1 day |
| 2171829 | 17 | ~1 day |
| 2326348 | 17 | ~1 day |
| 2411364 | 17 | ~1 day |
| 573667 | 12 | ~3 days |
| 3123117 | 17 | ~2 hours |
| 2459610 | 18 | ~1 day |
| 3219827 | 6 (dead) | crashed |
| **Total** | **126/126** | |
Note: `clear_stale_readers()` is called at env open, but it only reclaims slots from **dead** processes. Slots held by **alive** nvim processes are never released, even though each process should only need 1-2 reader slots at a time.
## Reproduction
1. Open multiple nvim sessions over time (7+ sessions running for 1+ days each)
2. In a new nvim session, trigger the fff file picker (e.g., `` or `ff`)
3. fff.nvim crashes with SIGSEGV, nvim exits
## Environment
- **OS**: Linux x86_64 (devcontainer, AMD EPYC 7K62)
- **Neovim version**: built from source (LazyVim distribution)
- **fff.nvim version**: `0.9.7-nightly.1a8ef35` (commit `1a8ef35`)
- **Binary**: prebuilt `x86_64-unknown-linux-gnu.so` downloaded from GitHub releases
- **Rustc** (for local build fallback): `1.96.1`
## Workaround
Delete the LMDB lock file to reset the reader table:
```bash
rm -f ~/.cache/nvim/fff_nvim/lock.mdb
rm -f ~/.local/share/nvim/fff_queries/lock.mdb
```
This preserves the frecency data in `data.mdb` but resets all reader slots. LMDB recreates the lock file on next open.
## Suggested Fix
The reader slot leak appears to be in the LMDB transaction handling — read transactions are opened but not properly closed/committed in some code paths, leaving reader slots occupied indefinitely. The `clear_stale_readers()` call at env open only helps with dead processes, not alive ones that have leaked slots.
Potential areas to investigate:
- `open_database_safe()` in `crates/fff-core/src/dbs/lmdb.rs` — read transactions are committed, but if any code path opens additional read transactions without proper cleanup, slots leak
- Background watcher / scan threads that may open read transactions
- The `SharedDb` refactor (commit `18f546b`) that replaced two LMDB handles — may have introduced the leak
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in crates/fff-core/src/dbs/lmdb.rs at open_database_safe() and trace every read transaction, including paths used by background watcher and scan threads. Compare the SharedDb refactor from commit 18f546b and reproduce with multiple long-running Neovim sessions until MDB_READERS_FULL occurs. Done means read slots are released for alive processes and the file picker no longer segfaults when the reader limit is reached.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- neovim, rust
- Domain
- databases
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 50/100