You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[tx] Cross-thread mutation of non-thread-safe transaction state on close/kill #4939
Location:DatabaseContext.java:118-131 (removeAllContexts calls map.remove(...) on plain HashMaps concurrently mutated by their owner threads via init/get/put), LocalDatabase.java:2056-2070 (closeInternal calls tx.rollback() on TransactionContexts owned by other threads that may be mid-commit; all TransactionContext fields are unsynchronized HashMaps).
Interleaving: thread A is inside commit1stPhase iterating modifiedPages; thread B closes the DB and closeInternal rolls back As tx concurrently, nulling modifiedPages/clearing caches -> NPE at best, corrupted HashMap / partial unlock at worst. Likewise removeAllContextsracingDatabaseContext.inits map.put` can lose entries or corrupt the table.
Fix: per-thread maps mutated by owner only; on close, set a volatile "invalidated" flag and let the owning thread roll back on next access, or make the per-thread map a ConcurrentHashMap and make rollback reject/tolerate foreign-thread invocation.
Filed from an internal engine deep-audit (2026-07). Traced to source; verify against current main before fixing and add a regression test.
Fixed in #5060 (merged). The genuine part of the finding - removeAllContexts() (database close/drop on another thread) mutating per-thread plain HashMaps concurrently with the owner's init()/get()/put() - is fixed: the per-thread maps are ConcurrentHashMap, and the closing thread no longer prunes foreign CONTEXTS registry entries (that removal raced the owner's re-registration and could unregister a live context forever). The reported mid-commit interleaving was verified NOT reproducible on current main: commit() runs entirely under the database read lock while closeInternal's rollbacks run under the write lock of the same RWLockContext, so they cannot overlap - documented rather than fixed-by-faith. Cross-thread rollback releases foreign locks correctly via the captureRequester()/getRequester() split (capture on the owner at lock-acquisition, pure read at release; volatile for the live-owner close path, JLS 17.4.4 happens-before for dead owners).
Activity
review(#4939,#4941,#4956): skip inactive transactions in the dead-thr…
Merge pull request #5060 from ArcadeData/fix/4939-4941-4956-dbcontext…
lvca commentedon Jul 7, 2026
Fixed in #5060 (merged). The genuine part of the finding - removeAllContexts() (database close/drop on another thread) mutating per-thread plain HashMaps concurrently with the owner's init()/get()/put() - is fixed: the per-thread maps are ConcurrentHashMap, and the closing thread no longer prunes foreign CONTEXTS registry entries (that removal raced the owner's re-registration and could unregister a live context forever). The reported mid-commit interleaving was verified NOT reproducible on current main: commit() runs entirely under the database read lock while closeInternal's rollbacks run under the write lock of the same RWLockContext, so they cannot overlap - documented rather than fixed-by-faith. Cross-thread rollback releases foreign locks correctly via the captureRequester()/getRequester() split (capture on the owner at lock-acquisition, pure read at release; volatile for the live-owner close path, JLS 17.4.4 happens-before for dead owners).
fix(ArcadeData#4939,ArcadeData#4941,ArcadeData#4956): DatabaseContext…
fix(ArcadeData#5067): low-severity audit residuals (kill() lock symme…
fix(#4939,#4941,#4956): DatabaseContext lifecycle - dead-thread sweep…
review(#4939,#4941,#4956): skip inactive transactions in the dead-thr…
fix(#5067): low-severity audit residuals (kill() lock symmetry, page-…