Giter Club home page Giter Club logo

Comments (4)

qwwdfsad avatar qwwdfsad commented on June 18, 2024

Thanks for the reproducer.

Unfortunately, I don't think we can do much here without a deep understanding of how live edit works, how and what exactly it reloads and how it is incorporated into the threading system, especially when live edit does not work along with the debugger.

I see two pieces of solid evidence it is a problem on the Live Edit side:

  • The bug would manifest itself in the environment without bytecode weaving, while in this reproducer it consistently reproduces every time LE is pushed and only then.
  • This snippet of code gives a direct suspect:
runBlocking() {
    Log.d("DEBUG", ">>>> F")
    Log.d("DEBUG", ">>>> Should be main: ${Thread.currentThread().name} having context: ${currentCoroutineContext()}")
    suspendCoroutineUninterceptedOrReturn<Unit> {
        Log.d("DEBUG", ">>> Proxy: ${it.intercepted()}")
        Unit
    }
    withContext(Executors.newCachedThreadPool().asCoroutineDispatcher()) {
        Log.d("DEBUG", ">>> Should be DefaultDispatcher-worker: ${Thread.currentThread().name} having context: ${currentCoroutineContext()}")
    }
    Log.d("DEBUG", ">>>> Should be main: ${Thread.currentThread().name} having context: ${currentCoroutineContext()}")
}

It prints >>> Proxy: com.android.tools.deploy.liveedit.ProxyClassHandler@8bd34b3 and kotlinx.coroutines is not prepared for situations where intercepted actual return type is changed from what our CoroutineDispatcher returned (which is an instance of DispatchedContinuation) to anything else. The default mode for kotlinx.coroutines to operate with alien intercepted contunations is "resume it directly in the same very thread as we have no other clues what to do"

from kotlinx.coroutines.

qwwdfsad avatar qwwdfsad commented on June 18, 2024

The best pointer I can give you to untie this problem further is to look at the simplified piece of the code:

runBlocking {
    withContext(Dispatchers.IO) {
        2
    }
    Unit
}

and debug step by step how the control is returned from withContext to outer coroutine.
It should go through the AbstractCoroutine.resumeWith -> DispatcherCoroutine.afterResume -> intercepted.resumeCancellableWith where the typecheck happens.

It might shed some light on the machinery in order to understand what suitable replacements can be made in ProxyClassHandler

from kotlinx.coroutines.

acleung avatar acleung commented on June 18, 2024

Thanks for the pointers, Vsevolod. I feel like we are getting really close to the issue.

Would you be able to explain to me how DispatchedContinuation comes into play in the examples? I have a hard time rationalizing how a ProxyClassHandler could suddenly replace a DispatchedContinuation object which is a final object that is not in any class's hierarchy that Live Edit wants to take over.

In that example, ProxyClassHandler@8bd34b3 was a Proxy handler object created for a lambda of the function (ContextBug$Companion$Launch$1 in my compiled output)

Instances of ContextBug$Companion$Launch$1 should be been represented by our proxy object. It is set up with the SuspendLambda super class with all the interfaces set up by getProxyClass so all instance-of checks needed by the Kotlin Coroutine runtime should have been properly handled.

from kotlinx.coroutines.

qwwdfsad avatar qwwdfsad commented on June 18, 2024

So, long story short, DispatchedContinuation wraps the original continuation and intercepts its resume method -- instead of direct resumption right in the stack, it schedules its execution in the corresponding dispatcher and also handles cancellations, thread-locals and other machinery.
Mostly because of historical reasons, we have a few places that directly type-check continuations into DispatchedContinuation (related: #2439), but AFAIR it's rather a performance optimization than something else.

The following emulation of the reproducer works as expected:

runBlocking<Unit> {
    println(Thread.currentThread())
    withContext(object : ContinuationInterceptor {
        override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
            val intercepted = Dispatchers.IO.interceptContinuation(continuation)
            return Continuation(intercepted.context) {
                intercepted.resumeWith(it)
            }
        }

        override val key: CoroutineContext.Key<*>
            get() = CoroutineDispatcher
    }) {
        println(Thread.currentThread())
        2
    }
    println(Thread.currentThread())

    Unit
}

which narrows down the suspect even further to proxy classes, probably worth checking them out and see what they are compiled into. Maybe ProxyClass loses the context when wrapping the continuation or maybe it replaces the class that was an instance of ContinuationImpl but now is not and interception fails [1].

[1] -- see https://github.com/JetBrains/kotlin/blob/438c55756f49ad4fe3d02c7502a1b16255848359/libraries/stdlib/jvm/src/kotlin/coroutines/intrinsics/IntrinsicsJvm.kt#L182

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.