Skip to content

fix: EXPOSED-1097 Make JDBC transaction context registry thread-safe - #2950

Open
Michael Hancock (mikehancock) wants to merge 2 commits into
JetBrains:mainfrom
mikehancock:codex/fix-transaction-context-registry-race
Open

Michael Hancock (mikehancock) wants to merge 2 commits into
JetBrains:mainfrom
mikehancock:codex/fix-transaction-context-registry-race

Conversation

@mikehancock

@mikehancock Michael Hancock (mikehancock) commented Oct 3, 2026 •

Copy link
Copy Markdown

Description

Summary of the change: TransactionManager.contextKeys is a plain LinkedHashMap read without synchronization while databases are registered and unregistered concurrently. This replaces it with a ConcurrentHashMap.

Detailed description:

  • Why: registerManager and closeAndUnregister are @Synchronized, but getContextKey reads contextKeys without any lock. A read that overlaps a resize can return null for a manager that is still registered, so getContextKey throws IllegalStateException: No context key found for transaction manager .... We hit this as intermittent HTTP 500s in an integration test suite with pooled test applications that each register their own Database. Related to the same class of problem fixed for IdentifierManagerApi in fix: fix thread-safety of IdentifierManagerApi identifier caches (#1704) #2783 (EXPOSED-1014).
  • What: contextKeys in the JDBC TransactionManager companion is now a ConcurrentHashMap. No public API changes.
  • How: One-line type change plus import. Unlike the IdentifiersCache in fix: fix thread-safety of IdentifierManagerApi identifier caches (#1704) #2783, contextKeys has no eviction policy, so ConcurrentHashMap loses nothing. The @Synchronized writers still work, and reads become safe without extra locking.

Added testContextKeysRemainAvailableDuringConcurrentDatabaseRegistration to CustomTransactionManagerTest. It reads existing managers' keys from 4 threads while registering many more mock databases (no SQL connections are opened), then unregisters them. It fails on unmodified main with the missing-context-key exception and passes with the fix.


Type of Change

  • Bug fix
  • New feature
  • Documentation update

Updates/remove existing public API methods:

  • Is breaking change

Affected databases:

  • None specifically. The registry is JVM-wide and database-independent, and the test uses mock databases without opening connections.

Validation

  • Regression test: fails on main, passes with the fix.
  • H2 transaction and coroutine tests: 99 passed, 19 skipped, 0 failed.
  • apiCheck and detekt pass for both affected modules.
  • Not run: other databases, since the change does not touch SQL or dialect code.

The R2DBC TransactionManager uses the same pattern (plain mutableMapOf, unsynchronized read in getContextKey). This PR is limited to JDBC; I'm happy to apply the same change to R2DBC here or in a follow-up, whichever you prefer.

Checklist

  • Unit tests are in place
  • The build is green (including the Detekt check)
  • All public methods affected by my PR have up-to-date API docs — no public API changes
  • Documentation for my change is up to date — no new API or configuration

Related Issues

EXPOSED-1097
Related: EXPOSED-1014 (#2783)

@mikehancock Michael Hancock (mikehancock) changed the title fix: make JDBC transaction context registry thread-safe fix: EXPOSED-1097 Make JDBC transaction context registry thread-safe Oct 11, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant