Wrap continuation with ContinuationImpl on callable references

in startCoroutineUninterceptedOrReturn. Otherwise, the coroutine will
not be interceptable later.

Add a test, which checks, that intercepted continuation is released.

 #KT-55869
This commit is contained in:
Ilmir Usmanov
2023-01-17 05:48:12 +01:00
committed by Space Team
parent 9a2cb2609f
commit d18672bfb1
23 changed files with 413 additions and 39 deletions
@@ -55,6 +55,17 @@ fun box(): String {
if (res != "OK") {
return "fail 3 $res"
}
res = builder(::suspendHere)
if (res != "OK") {
return "fail 4 $res"
}
res = builder {
suspend {}()
suspendHere()
}
if (res != "OK") {
return "fail 5 $res"
}
return "OK"
}
@@ -56,6 +56,30 @@ fun box(): String {
if (res != "OK") {
return "fail 3 $res"
}
res = c.builder {
suspend {}()
controllerSuspendHere()
}
if (res != "OK") {
return "fail 4 $res"
}
res = c.builder {
suspend {}()
controllerMultipleArgs(1, 1, 1)
}
if (res != "OK") {
return "fail 5 $res"
}
res = c.builder {
suspend {}()
if (coroutineContext != EmptyCoroutineContext)
"${coroutineContext} != $EmptyCoroutineContext"
else
"OK"
}
if (res != "OK") {
return "fail 6 $res"
}
return "OK"
}
@@ -55,6 +55,27 @@ fun box(): String {
if (res != "OK") {
return "fail 3 $res"
}
res = v.builder {
suspend {}()
controllerMultipleArgs(1, 1, 1)
}
if (res != "OK") {
return "fail 4 $res"
}
res = v.builder {
suspend {}()
if (coroutineContext == EmptyCoroutineContext) "$coroutineContext == $EmptyCoroutineContext" else "OK"
}
if (res != "OK") {
return "fail 5 $res"
}
res = v.builder {
suspend {}()
controllerSuspendHere()
}
if (res != "OK") {
return "fail 6 $res"
}
return "OK"
}
@@ -1,5 +1,6 @@
// WITH_STDLIB
// WITH_COROUTINES
// IGNORE_BACKEND: JS_IR, NATIVE
import helpers.*
import kotlin.coroutines.*
import kotlin.coroutines.intrinsics.*
@@ -25,28 +26,24 @@ suspend fun suspendWithExceptionIntercepted(): String = suspendCoroutineUninterc
COROUTINE_SUSPENDED
}
fun builder(expectedCount: Int, c: suspend () -> String): String {
fun builder(testNum: Int, expectedCount: Int, c: suspend () -> String): String {
var fromSuspension: String? = null
var counter = 0
val result = try {
c.startCoroutineUninterceptedOrReturn(object: ContinuationAdapter<String>() {
override val context: CoroutineContext
get() = ContinuationDispatcher { counter++ }
c.startCoroutineUninterceptedOrReturn(object : ContinuationAdapter<String>() {
override val context: CoroutineContext
get() = ContinuationDispatcher { counter++ }
override fun resumeWithException(exception: Throwable) {
fromSuspension = "Exception: " + exception.message!!
}
override fun resumeWithException(exception: Throwable) {
fromSuspension = "Exception: " + exception.message!!
}
override fun resume(value: String) {
fromSuspension = value
}
})
} catch (e: Exception) {
"Exception: ${e.message}"
}
override fun resume(value: String) {
fromSuspension = value
}
})
if (counter != expectedCount) throw RuntimeException("fail 0")
if (counter != expectedCount) throw RuntimeException("fail 0 $testNum $counter != $expectedCount")
return fromSuspension!!
}
@@ -72,11 +69,35 @@ private class DispatchedContinuation<T>(
}
fun box(): String {
if (builder(0) { suspendHereUnintercepted() } != "OK") return "fail 2"
if (builder(1) { suspendHereIntercepted() } != "OK") return "fail 3"
if (builder(0, 0) { suspendHereUnintercepted() } != "OK") return "fail 2"
if (builder(1, 1) { suspendHereIntercepted() } != "OK") return "fail 3"
if (builder(0) { suspendWithExceptionUnintercepted() } != "Exception: OK") return "fail 4"
if (builder(1) { suspendWithExceptionIntercepted() } != "Exception: OK") return "fail 5"
if (builder(2, 0) { suspendWithExceptionUnintercepted() } != "Exception: OK") return "fail 4"
if (builder(3, 1) { suspendWithExceptionIntercepted() } != "Exception: OK") return "fail 5"
if (builder(4, 0, ::suspendHereUnintercepted) != "OK") return "fail 6"
if (builder(5, 1, ::suspendHereIntercepted) != "OK") return "fail 7"
if (builder(6, 0, ::suspendWithExceptionUnintercepted) != "Exception: OK") return "fail 8"
if (builder(7, 1, ::suspendWithExceptionIntercepted) != "Exception: OK") return "fail 9"
if (builder(8, 0) {
suspend {}()
suspendHereUnintercepted()
} != "OK") return "fail 21"
if (builder(9, 1) {
suspend {}()
suspendHereIntercepted()
} != "OK") return "fail 31"
if (builder(10, 0) {
suspend {}()
suspendWithExceptionUnintercepted()
} != "Exception: OK") return "fail 41"
if (builder(11, 1) {
suspend {}()
suspendWithExceptionIntercepted()
} != "Exception: OK") return "fail 51"
return "OK"
}
@@ -0,0 +1,113 @@
// WITH_STDLIB
// IGNORE_BACKEND: JS_IR, NATIVE
import kotlin.coroutines.*
import kotlin.coroutines.intrinsics.*
var c: Continuation<String>? = null
var interceptions: Int = 0
var releases: Int = 0
var invocations: Int = 0
suspend fun suspendHere(): String = suspendCoroutineUninterceptedOrReturn { x ->
invocations++
c = x
COROUTINE_SUSPENDED
}
suspend fun suspendHereIntercepted(): String = suspendCoroutineUninterceptedOrReturn { x ->
invocations++
c = x.intercepted()
COROUTINE_SUSPENDED
}
fun builder(testNum: Int, expectedResult: Result<String>, c: suspend () -> String) {
c.startCoroutineUninterceptedOrReturn(object : Continuation<String> {
override val context: CoroutineContext
get() = ContinuationDispatcher()
override fun resumeWith(result: Result<String>) {
if (result != expectedResult) {
error("fail $testNum: unexpected result: $expectedResult != $result")
}
}
})
}
class ContinuationDispatcher : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> = DispatchedContinuation(continuation)
override fun releaseInterceptedContinuation(continuation: Continuation<*>) {
releases++
}
}
private class DispatchedContinuation<T>(
val continuation: Continuation<T>
): Continuation<T> {
init {
interceptions++
}
override val context: CoroutineContext = continuation.context
override fun resumeWith(result: Result<T>) {
continuation.resumeWith(result)
}
}
fun box(): String {
val success = Result.success("OK")
builder(0, success) { suspendHere() }
if (invocations != 1) return "fail 00: $invocations"
if (interceptions != 0) return "fail 01: $interceptions"
if (releases != 0) return "fail 02: $releases"
c = c?.intercepted()
if (interceptions != 1) return "fail 03: $interceptions"
c?.resumeWith(success)
if (releases != 1) return "fail 04: $releases"
builder(1, success) { suspendHereIntercepted() }
if (invocations != 2) return "fail 10: $invocations"
if (interceptions != 2) return "fail 11: $interceptions"
if (releases != 1) return "fail 12: $interceptions"
c?.resumeWith(success)
if (interceptions != 2) return "fail 13: $interceptions"
if (releases != 2) return "fail 14: $releases"
builder(2, success, ::suspendHere)
if (invocations != 3) return "fail 20: $invocations"
if (interceptions != 2) return "fail 21: $interceptions"
if (releases != 2) return "fail 22: $releases"
c = c?.intercepted()
if (interceptions != 3) return "fail 23: $interceptions"
c?.resumeWith(success)
if (releases != 3) return "fail 24: $releases"
builder(3, success, ::suspendHereIntercepted)
if (invocations != 4) return "fail 30: $invocations"
if (interceptions != 4) return "fail 31: $interceptions"
if (releases != 3) return "fail 32: $interceptions"
c?.resumeWith(success)
if (interceptions != 4) return "fail 33: $interceptions"
if (releases != 4) return "fail 34: $releases"
builder(4, success) { suspend {}(); suspendHere() }
if (invocations != 5) return "fail 40: $invocations"
if (interceptions != 4) return "fail 41: $interceptions"
if (releases != 4) return "fail 42: $releases"
c = c?.intercepted()
if (interceptions != 5) return "fail 43: $interceptions"
c?.resumeWith(success)
if (releases != 5) return "fail 44: $releases"
builder(5, success) { suspend {}(); suspendHereIntercepted() }
if (invocations != 6) return "fail 50: $invocations"
if (interceptions != 6) return "fail 51: $interceptions"
if (releases != 5) return "fail 52: $interceptions"
c?.resumeWith(success)
if (interceptions != 6) return "fail 53: $interceptions"
if (releases != 6) return "fail 54: $releases"
return "OK"
}
@@ -37,9 +37,20 @@ fun builder(c: suspend () -> String): String {
fun box(): String {
if (builder { "OK" } != "OK") return "fail 4"
if (builder { suspendHere() } != "OK") return "fail 5"
if (builder {
suspend {}()
suspendHere()
} != "OK") return "fail 51"
if (builder { throw RuntimeException("OK") } != "Exception: OK") return "fail 6"
if (builder { suspendWithException() } != "Exception: OK") return "fail 7"
if (builder {
suspend {}()
suspendWithException()
} != "Exception: OK") return "fail 71"
if (builder(::suspendHere) != "OK") return "fail 8"
if (builder(::suspendWithException) != "Exception: OK") return "fail 9"
return "OK"
}
@@ -48,9 +48,14 @@ fun builder(shouldSuspend: Boolean, c: suspend () -> String): String {
fun box(): String {
if (builder(false) { "OK" } != "OK") return "fail 4"
if (builder(true) { suspendHere() } != "OK") return "fail 5"
if (builder(true) { suspend{}(); suspendHere() } != "OK") return "fail 51"
if (builder(false) { throw RuntimeException("OK") } != "Exception: OK") return "fail 6"
if (builder(true) { suspendWithException() } != "Exception: OK") return "fail 7"
if (builder(true) { suspend{}(); suspendWithException() } != "Exception: OK") return "fail 71"
if (builder(true, ::suspendHere) != "OK") return "fail 8"
if (builder(true, ::suspendWithException) != "Exception: OK") return "fail 9"
return "OK"
}
@@ -1,5 +1,6 @@
// WITH_STDLIB
// WITH_COROUTINES
// IGNORE_BACKEND: NATIVE
import helpers.*
import kotlin.coroutines.*
import kotlin.coroutines.intrinsics.*
@@ -19,7 +20,7 @@ suspend fun suspendWithException(): String = suspendCoroutine { x ->
}
}
fun builder(shouldSuspend: Boolean, expectedCount: Int, c: suspend () -> String): String {
fun builder(testNum: Int, shouldSuspend: Boolean, expectedCount: Int, c: suspend () -> String): String {
callback = {}
var fromSuspension: String? = null
var counter = 0
@@ -43,15 +44,15 @@ fun builder(shouldSuspend: Boolean, expectedCount: Int, c: suspend () -> String)
callback()
if (counter != expectedCount) throw RuntimeException("fail 0")
if (counter != expectedCount) throw RuntimeException("fail 0 $testNum $counter != $expectedCount")
if (shouldSuspend) {
if (result !== COROUTINE_SUSPENDED) throw RuntimeException("fail 1")
if (fromSuspension == null) throw RuntimeException("fail 2")
if (result !== COROUTINE_SUSPENDED) throw RuntimeException("fail 1 $testNum $result")
if (fromSuspension == null) throw RuntimeException("fail 2 $testNum")
return fromSuspension!!
}
if (result === COROUTINE_SUSPENDED) throw RuntimeException("fail 3")
if (result === COROUTINE_SUSPENDED) throw RuntimeException("fail 3 $testNum")
return result as String
}
@@ -77,11 +78,16 @@ private class DispatchedContinuation<T>(
}
fun box(): String {
if (builder(false, 0) { "OK" } != "OK") return "fail 4"
if (builder(true, 1) { suspendHere() } != "OK") return "fail 5"
if (builder(0, false, 0) { "OK" } != "OK") return "fail 4"
if (builder(1, true, 1) { suspendHere() } != "OK") return "fail 5"
if (builder(2, true, 1) { suspend{}(); suspendHere() } != "OK") return "fail 51"
if (builder(false, 0) { throw RuntimeException("OK") } != "Exception: OK") return "fail 6"
if (builder(true, 1) { suspendWithException() } != "Exception: OK") return "fail 7"
if (builder(3, false, 0) { throw RuntimeException("OK") } != "Exception: OK") return "fail 6"
if (builder(4, true, 1) { suspendWithException() } != "Exception: OK") return "fail 7"
if (builder(5, true, 1) { suspend{}(); suspendWithException() } != "Exception: OK") return "fail 71"
if (builder(6, true, 1, ::suspendHere) != "OK") return "fail 8"
if (builder(7, true, 1, ::suspendWithException) != "Exception: OK") return "fail 9"
return "OK"
}
@@ -72,9 +72,14 @@ private class DispatchedContinuation<T>(
fun box(): String {
if (builder(false) { "OK" } != "OK") return "fail 4"
if (builder(true) { suspendHere() } != "OK") return "fail 5"
if (builder(true) { suspend{}(); suspendHere() } != "OK") return "fail 51"
if (builder(false) { throw RuntimeException("OK") } != "Exception: OK") return "fail 6"
if (builder(true) { suspendWithException() } != "Exception: OK") return "fail 7"
if (builder(true) { suspend{}(); suspendWithException() } != "Exception: OK") return "fail 71"
if (builder(true, ::suspendHere) != "OK") return "fail 8"
if (builder(true, ::suspendWithException) != "Exception: OK") return "fail 9"
return "OK"
}