fix(core): open LMDB envs WithoutTls to stop reader slot leak (#664)#665
fix(core): open LMDB envs WithoutTls to stop reader slot leak (#664)#665gustav-fff wants to merge 1 commit into
Conversation
Default heed EnvOpenOptions opens envs in WithTls mode, where LMDB pins reader locktable slots to OS threads. Every thread that ever runs read_txn() permanently occupies a slot for the process lifetime, even after commit(). fff opens read txns from many threads (rayon workers, background_watcher, fff-lmdb-gc, git_status_worker, neovim main), so a handful of long-running nvim sessions exhaust the default 126-slot table and new nvim processes crash with MDB_READERS_FULL. Switch to read_txn_without_tls() — reader slots are now tied to MDB_txn objects and released on commit()/drop(). Regression test spawns 400 short-lived reader threads against a fresh FrecencyTracker; pre-fix it fails around thread 127 with MDB_READERS_FULL, post-fix all 400 succeed.
|
I tested this PR against a real Environment:
Before the patch, the LMDB reader table had all 126 slots occupied across 11 live PIDs, with 10-12 slots per process. Every entry had I cherry-picked
This directly confirms that One test note: the current 400 short-lived-thread regression test also passed for me on unpatched This issue is reproducible in normal multi-process agent usage, and this PR resolved it here without changing the C/Node ABI. Please consider merging it soon. I am happy to test an updated branch as well. |
Closes #664
Root cause
Default
heed::EnvOpenOptionsopens envs inWithTlsmode. In this mode LMDB ties reader locktable slots to OS threads instead ofMDB_txnobjects, so any thread that ever callsread_txn()occupies a slot for the entire process lifetime — the slot is not released oncommit()ordrop().fff opens read txns from many threads: rayon workers in
BACKGROUND_THREAD_POOL, thebackground_watcher, thefff-lmdb-gcthread,git_status_worker, and the neovim main thread.maxreadersdefaults to 126, so a handful of long-running nvim sessions burn through the entire reader table. New nvim sessions then hitMDB_READERS_FULLinopen_database_safe()atcrates/fff-core/src/dbs/lmdb.rs:210, which propagates out asError::DbStartReadTxnand ultimately manifests as the SIGSEGV in the reporter's log:clear_stale_readers()at env open only reclaims slots from dead processes, so live nvim instances happily hoard them.Fix
Open the env with
.read_txn_without_tls()(heed 0.22'sWithoutTlsmarker). Reader slots are now tied toMDB_txnobjects and released oncommit()/drop(). Everything downstream (FrecencyTracker,QueryTracker,DbHealthChecker,LmdbStore) is retyped toEnv<WithoutTls>; no runtime behavior changes for callers.Steps to reproduce
Pre-fix, on
main(1a8ef35):Actual (pre-fix): fails ~thread 127 with
MDB_READERS_FULL: Environment maxreaders limit reached.Expected: all 400 threads complete successfully.
In the wild: 7+ long-running nvim sessions with fff.nvim loaded, then open picker in a new nvim — crashes with SIGSEGV.
How verified
cargo test -p fff-search --lib— 98 pass.cargo test -p fff-search --test lmdb_reader_slot_leak— new regression test passes; revertinglmdb.rsfails the workspace to compile, confirming the trait plumbing is what enforces the flag.cargo check --workspace— clean.Automated triage via Gustav. Honk-Honk 🪿