Skip to content

Commit

Permalink
Recover stacktraces for no-dispatched continuations, so recovery work…
Browse files Browse the repository at this point in the history
…s in 'suspend fun main' cases to further improve user experience

Fixes #1328
  • Loading branch information
qwwdfsad committed Jul 16, 2019
1 parent d100a3f commit f22604b
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 1 deletion.
9 changes: 8 additions & 1 deletion kotlinx-coroutines-core/common/src/Dispatched.kt
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,14 @@ internal fun <T> DispatchedTask<T>.resume(delegate: Continuation<T>, useMode: In
val state = takeState()
val exception = getExceptionalResult(state)
if (exception != null) {
delegate.resumeWithExceptionMode(exception, useMode)
/*
* Recover stacktrace for non-dispatched tasks.
* We usually do not recover stacktrace in a `resume` as all resumes go through `DispatchedTask.run`
* and we recover stacktraces there, but this is not the case for a `suspend fun main()` that knows nothing about
* kotlinx.coroutines and DispatchedTask
*/
val recovered = if (delegate is DispatchedTask<*>) exception else recoverStackTrace(exception, delegate)
delegate.resumeWithExceptionMode(recovered, useMode)
} else {
delegate.resumeMode(getSuccessfulResult(state), useMode)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ package kotlinx.coroutines.exceptions

import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
import kotlinx.coroutines.intrinsics.*
import kotlinx.coroutines.selects.*
import org.junit.Test
import java.util.concurrent.*
import kotlin.coroutines.*
import kotlin.test.*

/*
Expand Down Expand Up @@ -271,4 +273,40 @@ class StackTraceRecoveryTest : TestBase() {
checkCycles(e)
}
}


private suspend fun throws() {
yield() // TCE
throw RecoverableTestException()
}

private suspend fun awaiter() {
val task = GlobalScope.async(Dispatchers.Default, start = CoroutineStart.LAZY) { throws() }
task.await()
yield() // TCE
}

@Test
fun testNonDispatchedRecovery() {
val await = suspend { awaiter() }

val barrier = CyclicBarrier(2)
var exception: Throwable? = null
await.startCoroutineUnintercepted(Continuation(EmptyCoroutineContext) {
exception = it.exceptionOrNull()
barrier.await()
})

barrier.await()
val e = exception
assertNotNull(e)
verifyStackTrace(e, "kotlinx.coroutines.RecoverableTestException\n" +
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.throws(StackTraceRecoveryTest.kt:280)\n" +
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$throws\$1.invokeSuspend(StackTraceRecoveryTest.kt)\n" +
"\t(Coroutine boundary)\n" +
"\tat kotlinx.coroutines.DeferredCoroutine.await\$suspendImpl(Builders.common.kt:99)\n" +
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.awaiter(StackTraceRecoveryTest.kt:285)\n" +
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testNonDispatchedRecovery\$await\$1.invokeSuspend(StackTraceRecoveryTest.kt:291)\n" +
"Caused by: kotlinx.coroutines.RecoverableTestException")
}
}

0 comments on commit f22604b

Please sign in to comment.