Tags: init4tech/mdbx
Tags
feat: transparent cursor caching (ENG-2129) (#11) * feat: add cursor caching methods to Cache trait Extend DbCache with cursor pointer storage and add take_cursor, return_cursor, drain_cursors to the Cache trait. Both RefCell<DbCache> and SharedCache implement the new methods. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add cache field to Cursor, return pointer on drop Cursor now holds a &'tx K::Cache reference. Drop returns the raw pointer to the cache instead of calling mdbx_cursor_close. Add from_raw constructor for cache-hit path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: cache-aware Tx::cursor() and drain on commit/drop Tx::cursor() checks the cursor cache before allocating. Commit and drop paths drain cached cursors inside with_txn_ptr to ensure all FFI close calls are properly serialized. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: add cursor caching tests Verify cursor reuse across open/drop cycles, multiple cursors on the same DB, and repeated cycle stability. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs: document cursor caching Send/Sync and Drop constraint Document the unsafe Send + Sync impls on DbCache in CLAUDE.md with full justification. Add code comment explaining why Tx::Drop inlines drain logic instead of calling drain_cached_cursors (type system constraint: Drop is on Tx<K, U>, helper is on Tx<K>). * fix: drain cached cursors on close_db and drop_db Prevents use-after-free when a DBI is closed or dropped while stale cursor pointers for it remain in the cursor cache. * fix: prevent cache poisoning on mdbx_cursor_copy failure Construct Cursor only after mdbx_cursor_copy succeeds. On failure, close the raw pointer directly instead of letting Drop push an unbound cursor into the cache. * fix: renew cached cursors to reset B-tree position Call mdbx_cursor_renew on the cache-hit path so reused cursors start at a clean position rather than retaining stale state. * docs: add keep-in-sync comments on duplicated drain logic Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: add RO cursor-cache tests Cover cursor caching in read-only transactions (reuse and repeated cycles), complementing existing RW-only coverage. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: add cursor reuse across writes test Verify cached cursors see updated data after B-tree COW from interleaved put operations — the primary hot path for caching. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor: route cursor FFI through ops, harden renew error path, add cache tests Addresses Fraser's review on PR #11: - Extract drain-and-close logic into `close_drained_cursors` helper, used by `close_db`, `drop_db`, `drain_cached_cursors`, and `Tx::Drop`. - Move all cursor FFI calls into `ops` (`cursor_renew_raw`, `cursor_close_raw`, `cursor_close2_raw`), matching the crate convention. - On `mdbx_cursor_renew` failure in `Tx::cursor`, defensively close the raw pointer via `mdbx_cursor_close2` instead of leaking it. Not re-cached: a subsequent call would hit the same failure. - Add `#[cfg(test)]` `cursor_count` and `inject_cursor` to the `Cache` trait so tests can verify cache state directly. - Add tests: cache-count assertions, `close_db` drains its DBI only, and a renew-failure test that injects a null pointer to exercise the error path and confirm no leak. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(clippy): use checked_div for remaining_in_page calc Satisfies clippy::manual_checked_ops (new in Rust 1.95). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(clippy): use checked_div for remaining_in_page calc Satisfies clippy::manual_checked_ops (new in Rust 1.95). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(cursor-cache): co-locate cache with txn pointer to fix clone-drop drain `Tx::Drop` previously drained the shared cursor cache unconditionally. For `TxSync`, the cache was `Arc<RwLock<DbCache>>` shared across clones, so dropping any clone emptied the cache for all surviving clones. The naive `Arc::strong_count == 1` gate would race on concurrent drops and leak the cursor pointers (MDBX requires explicit `mdbx_cursor_close` regardless of RO/RW). Move the cache inside `PtrSync` (widening the existing `Mutex<()>` to `Mutex<DbCache>`) and `PtrUnsync` (`RefCell<DbCache>`). The `Arc` that controls the txn lifetime now also controls cache lifetime, so cursors are drained-and-closed exactly once in the inner Drop when refcount hits 0 — race-free by construction. Removes `SharedCache`, drops `Clone+Default` from the `Cache` trait, and trims the `cache` field/parameter from `Tx` and `Cursor`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: fix broken Tx::drop reference in close_drained_cursors Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: drop unresolvable intra-doc links on private PtrSync field Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: document deadlock risk of dropping Cursor inside with_txn_ptr Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: bump version to 0.8.3 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
PreviousNext