Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OOM due to NoTransactionInContextException in reactive pipeline #34048

Closed
ajax-kovalchuk-d opened this issue Dec 7, 2024 · 1 comment
Closed
Assignees
Labels
in: data Issues in data modules (jdbc, orm, oxm, tx) type: regression A bug that is also a regression
Milestone

Comments

@ajax-kovalchuk-d
Copy link

ajax-kovalchuk-d commented Dec 7, 2024

At commit 8e3846991d30a9a577dae9664996c036895f04cc in the Spring Framework, the NoTransactionInContextException was made a static instance. While this change can reduce overhead in non-reactive contexts, it introduces a potential issue in reactive pipelines: it can cause OutOfMemoryError (OOM) due to suppressed exceptions being accumulated.

In the Reactor Core implementation, as seen in the file FluxOnAssembly.java, the problem is appears at line 612:

Here, the NoTransactionInContextException is reused statically, and when it is thrown inside a reactive pipeline, it retains references to OnAssemblyException instance. This can lead to suppressed exceptions being stored indefinitely, creating a memory leak.

The behavior can be reproduced with the following Kotlin code:

/**
 * Stackless variant of [NoTransactionException] for reactive flows.
 */
private class NoTransactionInContextException : NoTransactionException("No transaction in context") {

    @Synchronized
    override fun fillInStackTrace(): Throwable {
        // Stackless exception
        return this
    }
}

private val NO_TRANSACTION_IN_CONTEXT_EXCEPTION = NoTransactionInContextException()

fun main() {
    repeat(1000) {
        runCatching {
            Flux.fromIterable((1..2))
                .flatMap<Unit> { Mono.error(NO_TRANSACTION_IN_CONTEXT_EXCEPTION) }
                .blockLast()
        }
    }

    System.gc()
}

Explanation of the Issue:

  • Each time the static NO_TRANSACTION_IN_CONTEXT_EXCEPTION is thrown within a reactive flow, the OnAssemblyException created by Reactor is added to the suppressed exceptions of NO_TRANSACTION_IN_CONTEXT_EXCEPTION.

  • Since the exception is static, it retains all these references, causing memory consumption to grow unbounded.

  • This pattern can quickly lead to an OutOfMemo`ryError, especially in high-throughput reactive applications.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Dec 7, 2024
@jhoeller jhoeller added in: data Issues in data modules (jdbc, orm, oxm, tx) type: regression A bug that is also a regression and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Dec 10, 2024
@jhoeller jhoeller added this to the 6.2.1 milestone Dec 10, 2024
@sdeleuze
Copy link
Contributor

Reverted after double-checking with @chemicL that the deferred exception accumulation mechanism is the founding technology for checkpoint() for instance, or used in block(), so static exception should be avoided with Reactor, as recently documented.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: data Issues in data modules (jdbc, orm, oxm, tx) type: regression A bug that is also a regression
Projects
None yet
Development

No branches or pull requests

4 participants