From 7513557315ece188fcdb94e7a7549d597785940a Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Mon, 25 Jun 2018 15:33:59 +0300 Subject: [PATCH] Unroll recursion in resumeWith Fixes KT-18987 --- .../jvm/internal/ContinuationImpl.kt | 63 +++++++++++-------- 1 file changed, 37 insertions(+), 26 deletions(-) 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 87f961d2db6..09b3fc8e562 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 @@ -31,19 +31,42 @@ internal abstract class RestrictedContinuationImpl protected constructor( public override val context: CoroutineContext get() = EmptyCoroutineContext - public override fun resumeWith(result: SuccessOrFailure) { - val completion = completion!! // fail fast when trying to resume continuation without completion - try { - val outcome = invokeSuspend(result) - if (outcome === CoroutineSingletons.COROUTINE_SUSPENDED) return - completion.resume(outcome) - } catch (exception: Throwable) { - completion.resumeWithException(exception) + // This implementation is final that it is fundamentally used to unroll resumeWith recursion + public final override fun resumeWith(result: SuccessOrFailure) { + 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 + val outcome: SuccessOrFailure = + try { + val outcome = invokeSuspend(param) + if (outcome === CoroutineSingletons.COROUTINE_SUSPENDED) return + SuccessOrFailure.success(outcome) + } catch (exception: Throwable) { + SuccessOrFailure.failure(exception) + } + releaseIntercepted() // this state machine instance is terminating + if (completion is RestrictedContinuationImpl) { + // unrolling recursion via loop + current = completion + param = outcome + } else { + // top-level completion reached -- invoke and return + completion.resumeWith(outcome) + return + } + } } } protected abstract fun invokeSuspend(result: SuccessOrFailure): Any? + protected open fun releaseIntercepted() { + // does nothing here, overridden in ContinuationImpl + } + public open fun create(completion: Continuation<*>): Continuation { throw UnsupportedOperationException("create(Continuation) has not been overridden") } @@ -61,11 +84,11 @@ internal abstract class ContinuationImpl protected constructor( ) : RestrictedContinuationImpl(completion) { protected constructor(completion: Continuation?) : this(completion, completion?.context) - override fun validateContext() { + protected override fun validateContext() { // nothing to do here -- supports any context } - override val context: CoroutineContext + public override val context: CoroutineContext get() = _context!! @Transient @@ -76,20 +99,7 @@ internal abstract class ContinuationImpl protected constructor( ?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this) .also { intercepted = this } - public override fun resumeWith(result: SuccessOrFailure) { - val completion = completion!! // fail fast when trying to resume continuation without completion - try { - val outcome = invokeSuspend(result) - if (outcome === CoroutineSingletons.COROUTINE_SUSPENDED) return - releaseIntercepted() - completion.resume(outcome) - } catch (exception: Throwable) { - releaseIntercepted() - completion.resumeWithException(exception) - } - } - - private fun releaseIntercepted() { + protected override fun releaseIntercepted() { val intercepted = intercepted if (intercepted != null && intercepted != this) { context[ContinuationInterceptor]!!.releaseInterceptedContinuation(intercepted) @@ -97,13 +107,12 @@ internal abstract class ContinuationImpl protected constructor( this.intercepted = CompletedContinuation // just in case } - override fun toString(): String { + public override fun toString(): String { // todo: how continuation shall be rendered? return "Continuation @ ${this::class.java.name}" } } -// todo: Do we really need it? internal object CompletedContinuation : Continuation { override val context: CoroutineContext get() = error("This continuation is already complete") @@ -111,6 +120,8 @@ internal object CompletedContinuation : Continuation { override fun resumeWith(result: SuccessOrFailure) { error("This continuation is already complete") } + + override fun toString(): String = "This continuation is already complete" } @SinceKotlin("1.3")