Skip to content

Invalid transaction synchronization propagation behavior when used with multiple transaction managers  #33691

@fufler

Description

@fufler

Affects: 6.1.13


Transaction synchronization mechanism works incorrectly when used with nested (in terms of invocation) transactions controlled by multiple transaction managers. Assume having service like this:

class ServiceImpl(
    private val eventPublisher: ApplicationEventPublisher
) {
    private fun doWork(name: String, block: () -> Unit) {
        eventPublisher.publishEvent(name)
        block()
    }

    @Transactional("txm1")
    fun useTxManager1(name: String, block: () -> Unit) {
        doWork(name, block)
    }

    @Transactional("txm2")
    fun useTxManager2(name: String, block: () -> Unit) {
        doWork(name, block)
    }
}

Then attempt to run «nested» transactions like this

@TransactionalEventListener(phase = AFTER_COMMIT)
fun afterCommitListener(e: Any) {
    events.add("after-commit:$e")
}

@TransactionalEventListener(phase = AFTER_ROLLBACK)
fun afterRollbackListener(e: Any) {
    events.add("after-rollback:$e")
}

fun test() {
    serviceImpl.useTxManager1("txm1-outer") {
        serviceImpl.useTxManager2("txm2") {
            serviceImpl.useTxManager1("txm1-inner") { }
        }

        throw NullPointerException()
    }
}

leads to an invalid sequence of events:

after-commit:txm2
after-commit:txm1-inner
after-rollback:txm1-outer

Event published inside txm1-inner scope gets caught by after-commit listener despite whole transaction is rolled back. Transaction mechanism itself works correctly, I've tested it using real tx managers implementations with H2 as datasource. This issue is related to the synchronization mechanism only.

I took a look at the corresponding source code and believe the main reason for such behavior is that synchronization is not suspended when participating in existing transaction. I'm not sure what proper fix should be, but it's clear that participating in existing transaction does not mean current synchronization should be used because in «nested» scenario synchronization may be left from enclosing transaction managed by another transaction manager.

Full problem reproducer may be found in the file attached to this issue. Just run mvn test to run all tests. Take a look at the syncId value printed during test to find relation between tx scope and synchronization in use. Feel free to ask if more details are required.

spring-inconsistent-tx-synchronization-reproducer.zip

Metadata

Metadata

Assignees

Labels

in: dataIssues in data modules (jdbc, orm, oxm, tx)status: invalidAn issue that we don't feel is valid

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions