#35744 coins: prevent DB resize from invalidating cursors
https://github.com/bitcoin/bitcoin/pull/35744 · · +194/-49 in 9 files, 8 commits · labels: UTXO Db and Indexes
Goal
- Prevent node crashes during concurrent UTXO scans and AssumeUTXO cache rebalancing
- Ensure UTXO database iterators remain valid when resizing the cache
This pull request prevents `CCoinsViewDB::ResizeCache()` from destroying active LevelDB iterators and causing an abort during AssumeUTXO cache rebalancing. It introduces a shared lock (`m_db_mutex`) held by active cursors and compaction while requiring exclusive locking for cache resizing. To support this safely, it extends Bitcoin Core's synchronization infrastructure with checked `SharedMutex` and `SharedLock` wrappers and updates `DEBUG_LOCKORDER` to handle non-LIFO lock release.
Problem: RPCs like `gettxoutsetinfo`, `scantxoutset`, and `dumptxoutset` retain LevelDB cursors after releasing `cs_main`. If AssumeUTXO cache rebalancing calls `ResizeCache()` concurrently, it replaces `m_db` and triggers a LevelDB assertion failure or abort. Furthermore, existing shared mutex usage bypassed lock order debugging and static thread safety analysis.
Category: Validation (#7 of 48)
P2 · bug fix
- P2 because it prevents node aborts during concurrent UTXO scans and AssumeUTXO rebalancing
- Keeps database iterators valid until destroyed so cache resizing cannot trigger crashes
Prevents a node abort in LevelDB when AssumeUTXO cache resizing races with active UTXO set scans. A crash during node operation due to concurrent RPC execution and chainstate sync is a clear stability issue.
Membership: Modifies CCoinsViewDB cursor management and cache resizing in src/txdb.cpp and src/txdb.h.
Factors: security/stability 2, bug 2, performance 0, user value 1, leverage 0
Category: Utilities (logging, arguments, libraries) (#6 of 66)
P2 · new feature
- P2 because it adds shared mutex primitives with debug lockorder tracking to the codebase
- Enables non-LIFO lock releases without corrupting debug lock tracking state
Brings shared locking into the project's lock-order tracking and Clang thread-safety analysis frameworks. It also fixes lock tracking corruption when shared locks outlive enclosing critical sections.
Membership: Extends synchronization primitives in src/sync.cpp and src/sync.h with SharedMutex, SharedLock, and non-LIFO lock release detection.
Factors: security/stability 2, bug 1, performance 0, user value 0, leverage 2
Reviewability: Ready
- Ready for review
- Cleanly addresses reviewer feedback with passing CI and unit tests
The code incorporates reviewer suggestions, cleanly passes CI, and has no outstanding questions or blocking conflicts.
Author status: active; addressed all reviewer design suggestions in updated commits
Resolved concerns:
- Replacing an atomic counter approach with a shared mutex to avoid custom synchronization logic
- Handling out-of-order (non-LIFO) lock releases in DEBUG_LOCKORDER when long-lived shared locks outlive outer locks
- Ensuring AssertLockHeld(cs_main) is checked during ResizeCache
Agreement: Strong
- Strong collaboration shaping the shared mutex and sync framework design (andrewtoth)
- Suggested integrating shared locks into lockorder tracking with non-LIFO releases (andrewtoth)
- Concept approval without stated reasons (sedited)
Strong: andrewtoth collaboratively guided the shared-locking design and co-authored commits, accompanied by a Concept ACK from sedited.
Reviewers have substantively shaped and supported the PR, with design suggestions integrated and no open objections remaining.
- andrewtoth proposed the shared mutex design on 2026-07-18 and provided lock-checking diffs on 2026-07-26
- sedited left a Concept ACK on 2026-09-08
Objections:
| Reviewer | Kind | Harm | Status | Blocking | Author replied | Quote |
|---|---|---|---|---|---|---|
| andrewtoth | approach | Atomic counter approach was non-standard compared to using a shared mutex for read/write synchronization | resolved | no | yes | 2026-07-18: "I'm not sure about this approach with an atomic counter though. Would it make sense to instead have a shared mutex..." Settled: 2026-07-25: l0rinc: "Thanks, took your suggestions, @andrewtoth. I reworked the fix around it..." |
Support:
- andrewtoth: Favored the shared mutex approach and provided code snippets to extend sync.cpp for non-LIFO lock checking
- sedited: Concept ACK [not substantive]
Participants: andrewtoth (objection), sedited (support)
State derived from the lists: substantive support, no open objection (andrewtoth)
Review verdicts (DrahtBot): 0
- Concept ACK: sedited
Files
114 lines under test/bench/ci.
- src/sync.h +51/-15
- src/test/coins_tests.cpp +52/-3
- src/test/sync_tests.cpp +48/-0
- src/sync.cpp +17/-5
- src/txdb.cpp +9/-7
- src/txdb.h +7/-6
- src/test/cuckoocache_tests.cpp +5/-6
- src/script/sigcache.cpp +2/-4
- src/script/sigcache.h +3/-3
Card
This PR fixes a bug where AssumeUTXO cache rebalancing via ResizeCache() can invalidate live LevelDB iterators held by gettxoutsetinfo or scantxoutset, causing an abort. It ensures cursors and compaction hold a shared lock while cache resizing requires exclusive access to the DB wrapper. To enable this safely, it adds checked SharedMutex wrappers and non-LIFO lock tracking to Bitcoin Core's synchronization utilities. Review is in a ready state with strong collaboration from andrewtoth and a Concept ACK from sedited.