From 0b59061bfea40500ac4bfad8a491a33ddf716860 Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Sun, 15 Jul 2018 19:37:46 +0300 Subject: [PATCH] Coroutines debug probes --- .../coroutines/intrinsics/IntrinsicsJvm.kt | 22 +++--- .../jvm/internal/ContinuationImpl.kt | 9 ++- .../coroutines/jvm/internal/DebugProbes.kt | 75 +++++++++++++++++++ 3 files changed, 94 insertions(+), 12 deletions(-) create mode 100644 libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/jvm/internal/DebugProbes.kt diff --git a/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/intrinsics/IntrinsicsJvm.kt b/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/intrinsics/IntrinsicsJvm.kt index 543d18f7730..879efd7d832 100644 --- a/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/intrinsics/IntrinsicsJvm.kt +++ b/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/intrinsics/IntrinsicsJvm.kt @@ -77,13 +77,15 @@ public actual inline fun (suspend R.() -> T).startCoroutineUninterceptedO @SinceKotlin("1.3") public actual fun (suspend () -> T).createCoroutineUnintercepted( completion: Continuation -): Continuation = - if (this is BaseContinuationImpl) - create(completion) +): Continuation { + val probeCompletion = probeCoroutineCreated(completion) + return if (this is BaseContinuationImpl) + create(probeCompletion) else - createCoroutineFromSuspendFunction(completion) { + createCoroutineFromSuspendFunction(probeCompletion) { (this as Function1, Any?>).invoke(it) } +} /** * Creates unintercepted coroutine with receiver type [R] and result type [T]. @@ -110,14 +112,16 @@ public actual fun (suspend () -> T).createCoroutineUnintercepted( public actual fun (suspend R.() -> T).createCoroutineUnintercepted( receiver: R, completion: Continuation -): Continuation = - if (this is BaseContinuationImpl) - create(receiver, completion) +): Continuation { + val probeCompletion = probeCoroutineCreated(completion) + return if (this is BaseContinuationImpl) + create(receiver, probeCompletion) else { - createCoroutineFromSuspendFunction(completion) { + createCoroutineFromSuspendFunction(probeCompletion) { (this as Function2, Any?>).invoke(receiver, it) } } +} /** * Intercepts this continuation with [ContinuationInterceptor]. @@ -175,7 +179,7 @@ private inline fun createCoroutineFromSuspendFunction( else object : ContinuationImpl(completion as Continuation, context) { private var label = 0 - + override fun invokeSuspend(result: SuccessOrFailure): Any? = when (label) { 0 -> { diff --git a/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/jvm/internal/ContinuationImpl.kt b/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/jvm/internal/ContinuationImpl.kt index d6296875d3c..10c0e83c64c 100644 --- a/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/jvm/internal/ContinuationImpl.kt +++ b/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/jvm/internal/ContinuationImpl.kt @@ -13,14 +13,17 @@ import kotlin.jvm.internal.Reflection @SinceKotlin("1.3") internal abstract class BaseContinuationImpl( - @JvmField - protected val completion: Continuation? + // This is `public val` so that it is private on JVM and cannot be modified by untrusted code, yet + // it has a public getter (since even untrusted code is allowed to inspect its call stack). + public val completion: Continuation? ) : Continuation, Serializable { // This implementation is final. This fact is used to unroll resumeWith recursion. public final override fun resumeWith(result: SuccessOrFailure) { + // Invoke "resume" debug probe only once, even if previous frames are "resumed" in the loop below, too + probeCoroutineResumed(this) + // This loop unrolls recursion in current.resumeWith(param) to make saner and shorter stack traces on resume var current = this var param = result - // This loop unrolls recursion in current.resumeWith(param) to make saner and shorter stack traces on resume while (true) { with(current) { val completion = completion!! // fail fast when trying to resume continuation without completion diff --git a/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/jvm/internal/DebugProbes.kt b/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/jvm/internal/DebugProbes.kt new file mode 100644 index 00000000000..2ba6d1903c8 --- /dev/null +++ b/libraries/stdlib/coroutines/jvm/src/kotlin/coroutines/jvm/internal/DebugProbes.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package kotlin.coroutines.jvm.internal + +import kotlin.coroutines.Continuation +import kotlin.coroutines.intrinsics.* + +/** + * This probe is invoked when coroutine is being created and it can replace completion + * with its own wrapped object to intercept completion of this coroutine. + * + * This probe is invoked from stdlib implementation of [createCoroutineUnintercepted] function. + * + * Once created, coroutine is repeatedly [resumed][probeCoroutineResumed] and [suspended][probeCoroutineSuspended], + * until it is complete. On completion, the object that was returned by this probe is invoked. + * + * ``` + * +-------+ probeCoroutineCreated +-----------+ + * | START | ---------------------->| SUSPENDED | + * +-------+ +-----------+ + * probeCoroutineResumed | ^ probeCoroutineSuspended + * V | + * +------------+ completion invoked +-----------+ + * | RUNNING | ------------------->| COMPLETED | + * +------------+ +-----------+ + * ``` + * + * While the coroutine is resumed and suspended, it is represented by the pointer to its `frame` + * which always extends [BaseContinuationImpl] and represents a pointer to the topmost frame of the + * coroutine. Each [BaseContinuationImpl] object has [completion][BaseContinuationImpl.completion] reference + * that points either to another frame (extending [BaseContinuationImpl]) or to the completion object + * that was returned by this `probeCoroutineCreated` function. + * + * When coroutine is [suspended][probeCoroutineSuspended], then it is later [resumed][probeCoroutineResumed] + * with a reference to the same frame. However, while coroutine is running it can unwind its frames and + * invoke other suspending functions, so its next suspension can happen with a different frame pointer. + */ +@SinceKotlin("1.3") +internal fun probeCoroutineCreated(completion: Continuation): Continuation { + /** implementation of this function is replaced by debugger */ + return completion +} + +/** + * This probe is invoked when coroutine is resumed using [Continuation.resumeWith]. + * + * This probe is invoked from stdlib implementation of [BaseContinuationImpl.resumeWith] function. + * + * Coroutines machinery implementation guarantees that the actual [frame] instance extends + * [BaseContinuationImpl] class, despite the fact that the declared type of [frame] + * parameter in this function is `Continuation<*>`. See [probeCoroutineCreated] for details. + */ +@SinceKotlin("1.3") +internal fun probeCoroutineResumed(frame: Continuation<*>) { + /** implementation of this function is replaced by debugger */ +} + +/** + * This probe is invoked when coroutine is suspended using [suspendCoroutineUninterceptedOrReturn], that is + * when the corresponding `block` returns [COROUTINE_SUSPENDED]. + * + * This probe is invoked from compiler-generated intrinsic for [suspendCoroutineUninterceptedOrReturn] function. + * + * Coroutines machinery implementation guarantees that the actual [frame] instance extends + * [BaseContinuationImpl] class, despite the fact that the declared type of [frame] + * parameter in this function is `Continuation<*>`. See [probeCoroutineCreated] for details. + */ +@SinceKotlin("1.3") +internal fun probeCoroutineSuspended(frame: Continuation<*>) { + /** implementation of this function is replaced by debugger */ +} +