Skip to content

Commit

Permalink
[DRAFT] Add scope DSL (problem)
Browse files Browse the repository at this point in the history
Signed-off-by: Trol <jiaoxiaodong@xiaomi.com>
  • Loading branch information
Trol committed Apr 25, 2020
1 parent f410298 commit 450e9df
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 10 deletions.
12 changes: 9 additions & 3 deletions kotlinx-coroutines-core/api/kotlinx-coroutines-core.api
Expand Up @@ -87,8 +87,8 @@ public final class kotlinx/coroutines/CancellableContinuationKt {
}

public final class kotlinx/coroutines/CancellationPointKt {
public static final fun runInterruptible (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun runInterruptible$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public static final fun runInterruptible (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun runInterruptible$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
}

public abstract interface class kotlinx/coroutines/ChildHandle : kotlinx/coroutines/DisposableHandle {
Expand All @@ -108,6 +108,9 @@ public final class kotlinx/coroutines/ChildJob$DefaultImpls {
public static fun plus (Lkotlinx/coroutines/ChildJob;Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/Job;
}

public abstract interface class kotlinx/coroutines/CodeScope {
}

public abstract interface class kotlinx/coroutines/CompletableDeferred : kotlinx/coroutines/Deferred {
public abstract fun complete (Ljava/lang/Object;)Z
public abstract fun completeExceptionally (Ljava/lang/Throwable;)Z
Expand Down Expand Up @@ -207,7 +210,7 @@ public final class kotlinx/coroutines/CoroutineName : kotlin/coroutines/Abstract
public final class kotlinx/coroutines/CoroutineName$Key : kotlin/coroutines/CoroutineContext$Key {
}

public abstract interface class kotlinx/coroutines/CoroutineScope {
public abstract interface class kotlinx/coroutines/CoroutineScope : kotlinx/coroutines/CodeScope {
public abstract fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext;
}

Expand Down Expand Up @@ -333,6 +336,9 @@ public final class kotlinx/coroutines/GlobalScope : kotlinx/coroutines/Coroutine
public abstract interface annotation class kotlinx/coroutines/InternalCoroutinesApi : java/lang/annotation/Annotation {
}

public abstract interface class kotlinx/coroutines/InterruptibleScope : kotlinx/coroutines/CodeScope {
}

public abstract interface class kotlinx/coroutines/Job : kotlin/coroutines/CoroutineContext$Element {
public static final field Key Lkotlinx/coroutines/Job$Key;
public abstract fun attachChild (Lkotlinx/coroutines/ChildJob;)Lkotlinx/coroutines/ChildHandle;
Expand Down
35 changes: 35 additions & 0 deletions kotlinx-coroutines-core/common/src/CodeScope.kt
@@ -0,0 +1,35 @@
/*
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.coroutines

/**
* Interface for code scopes that prevents scope code from accessing outer code scope methods (using outer code
* scopes as implicit receivers).
*
* Example (on JVM):
* ```
* runBlocking {
* // receiver `this` is an [CoroutineScope]
*
* runInterruptible {
* // receiver `this` is an [InterruptibleScope]
*
* launch { /* ... */ } // <- Should not run in the scope of runBlocking. And won't compile.
* queue.take()
* }
* }
* ```
*/
@InternalCoroutinesApi
@CodeScopeDsl
public interface CodeScope

/**
* DslMarker for CodeScope.
*/
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS)
@DslMarker
internal annotation class CodeScopeDsl
2 changes: 1 addition & 1 deletion kotlinx-coroutines-core/common/src/CoroutineScope.kt
Expand Up @@ -59,7 +59,7 @@ import kotlin.coroutines.intrinsics.*
* }
* ```
*/
public interface CoroutineScope {
public interface CoroutineScope: CodeScope {
/**
* The context of this scope.
* Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
Expand Down
Expand Up @@ -151,7 +151,7 @@ class ProduceTest : TestBase() {
launch {
expect(2)
assertFailsWith<IllegalStateException> {
awaitClose { expectUnreached() }
awaitClose { expectUnreached() } // <- access outer ProducerScope from launch scope, won't compile
expectUnreached()
}
}
Expand Down
18 changes: 14 additions & 4 deletions kotlinx-coroutines-core/jvm/src/CancellationPoint.kt
Expand Up @@ -51,23 +51,31 @@ import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
*/
public suspend fun <T> runInterruptible(
context: CoroutineContext = EmptyCoroutineContext,
block: () -> T
block: InterruptibleScope.() -> T
): T {
// fast path: empty context
if (context === EmptyCoroutineContext) { return runInterruptibleInExpectedContext(block) }
// slow path:
return withContext(context) { runInterruptibleInExpectedContext(block) }
}

private suspend fun <T> runInterruptibleInExpectedContext(block: () -> T): T =
/**
* Defines a scope for new interruptible blocking code blocks.
*
* Currently, The main goal is to restrict access to outer code scope methods (using outer scopes as
* implicit receivers). See [CodeScope] for details.
*/
public interface InterruptibleScope: CodeScope

private suspend fun <T> runInterruptibleInExpectedContext(block: InterruptibleScope.() -> T): T =
suspendCoroutineUninterceptedOrReturn sc@{ uCont ->
try {
// fast path: no job
val job = uCont.context[Job] ?: return@sc block()
val job = uCont.context[Job] ?: return@sc DefaultInterruptibleScope.block()
// slow path
val threadState = ThreadState().apply { initInterrupt(job) }
try {
block()
DefaultInterruptibleScope.block()
} finally {
threadState.clearInterrupt()
}
Expand All @@ -76,6 +84,8 @@ private suspend fun <T> runInterruptibleInExpectedContext(block: () -> T): T =
}
}

private object DefaultInterruptibleScope : InterruptibleScope

private class ThreadState {

fun initInterrupt(job: Job) {
Expand Down
Expand Up @@ -8,7 +8,11 @@ import java.io.IOException
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import kotlin.test.*
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotEquals
import kotlin.test.fail
import kotlin.test.Test

class InterruptibleCancellationPointTest: TestBase() {

Expand Down Expand Up @@ -165,6 +169,20 @@ class InterruptibleCancellationPointTest: TestBase() {
}
}

@Test
fun testCodeScopeDsl() {
runBlocking {
with("OtherScope") {
runInterruptible {
doSomethingUsefulBlocking(1, 0) // works as normal

// launch {} // using outer code scopes, won't compile.
substring(0) // using other outer scopes, works as normal
}
}
}
}

private fun doSomethingUsefulBlocking(timeUseMillis: Long, result: Int): Int {
Thread.sleep(timeUseMillis)
return result
Expand Down

0 comments on commit 450e9df

Please sign in to comment.