Skip to content

[tx] Cross-thread mutation of non-thread-safe transaction state on close/kill #4939

Closed

Description

@lvca

Severity: MEDIUM

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.

Activity

  1. added
    auditFindings from the 2026-07 engine deep audit
    concurrencyThreading / concurrency / MVCC
    on Jul 3, 2026
  2. added this to the 26.8.1 milestone on Jul 4, 2026
  3. self-assigned this
    on Jul 6, 2026
  4. added theissue type on Jul 6, 2026
  5. added 2 commits that reference this issue on Jul 6, 2026
  6. lvca commented on Jul 7, 2026

    @lvca
    MemberAuthor

    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).

  7. added 2 commits that reference this issue on Jul 7, 2026
  8. added 3 commits that reference this issue on Aug 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

auditFindings from the 2026-07 engine deep auditbugconcurrencyThreading / concurrency / MVCC

Type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions