Giter Club home page Giter Club logo

Comments (3)

yzhaoa avatar yzhaoa commented on August 16, 2024

Root cause

The root cause seems to be a bad interaction between CompletionStage.asDeferred and CompletableFuture. Specifically:

  1. When future completes, it calls the CompletionStage.handle callback at .
  2. The callback calls result.completeExceptionally.
  3. Completion of result triggers cancelFutureOnCompletion at
    result.cancelFutureOnCompletion(future)
    .
  4. CompletableFuture.cancel triggers postComplete at https://github.com/openjdk/jdk/blob/jdk-21%2B26/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java#L2512, which in turn runs (1) for the next Deferred, recursively.

Normally CompletableFuture runs all the callbacks iteratively, but due to triggering the cancel() function in a recursive context, it starts running callbacks recursively, and causes a stack overflow.

from kotlinx.coroutines.

yzhaoa avatar yzhaoa commented on August 16, 2024

Workaround

The above issue can be worked around by breaking the recursive CompletableFuture.cancel call. For example:

fun <T> CompletableFuture<T>.safeAsDeferred(): Deferred<T> {
    val parent = this
    val safeCompletableFuture = object : CompletableFuture<T>() {
        override fun cancel(mayInterruptIfRunning: Boolean): Boolean {
            return if (!parent.isDone) {
                parent.cancel(mayInterruptIfRunning)
            } else {
                parent.isCancelled
            }
        }
    }
    parent.handle {
        result, exception ->
        if (exception == null) {
            safeCompletableFuture.complete(result)
        } else {
            safeCompletableFuture.completeExceptionally(exception)
        }
    }
    return safeCompletableFuture.asDeferred()
}

When used in place of asDeferred in the reproducer above, this fixes the issue:

$ JAVA_HOME=/opt/homebrew/Cellar/openjdk@21/21.0.3/libexec/openjdk.jdk/Contents/Home kotlin test.main.kts 10000
Expected additional transformation to run. Actual: true
Expected 10000 Deferred<Unit> to complete exceptionally. Actual: 10000

And if you don't care about cancelling the Deferred, you can simply create another chained CompletableFuture, since apparently CompletableFuture cancellations doesn't propagate upstream:

future.thenApply { it }.asDeferred()

from kotlinx.coroutines.

dkhalanskyjb avatar dkhalanskyjb commented on August 16, 2024

Great job figuring out the problem!

I think this is worth reporting to the JDK maintainers as well. As far as I can see, nothing in the docs indicates that this may happen. Though nothing says that it won't, I think:

I think this should be fixed on the side of the JDK as well by detecting the same thread entering the cancellation procedure more than once. That said, we can work around this explicitly on our side: #4173 The downside of this workaround is that when several threads cancel coroutines related to one future, only one of them will deal with the work related to the cancellation now (as opposed to many threads sharing the load), but I don't think that's a significant issue.

from kotlinx.coroutines.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.