Comments (4)
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.
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.
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.
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].
from kotlinx.coroutines.
Related Issues (20)
- Ability to specify a limited dispatcher name HOT 2
- Proguard will remove SwingDispatcherFactory (MainDispatcherFactory) on JVM HOT 10
- `MutexImpl.unlock()` hangs forever when locked through `Semaphore.aquire()` HOT 4
- No artifacts published for Kotlin/WASM? (also affects kotlinx-serialization) HOT 4
- High CPU usate in SharedFlowImpl HOT 6
- Enum deserialization options HOT 1
- flatMapLatest + combine + first leads to execution while cancelled HOT 5
- Flow hangs even when using terminal operators HOT 11
- Change KotlinX Coroutines versioning to be SemVer-compliant HOT 1
- NPE thrown for mock Exceptions in "createFinalException" HOT 1
- Coroutines perform unnecessary thread switches HOT 20
- TestTimeSource/currentTime does not increase after switching to a non-delay skipping Dispatcher HOT 3
- Support `CoroutineName`, which is not propagated to child coroutines
- Sending to full Channel<Unit>(CONFLATED) is ~10x slower than sending to full Channel<Unit>(1) HOT 1
- RC: 1.4.3
- RC:1.4.3 HOT 4
- Noticeable upfront cost of Dispatchers.Default HOT 1
- Unresolved reference for kotlinx.coroutines on iOS HOT 6
- The coroutine states in the debugger are inaccurate when starts are undispatched HOT 1
- `withTimeout` with a long duration keeps the `DefaultExecutor` thread alive for much longer than necessary HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from kotlinx.coroutines.