Skip to content

Commit 450e9df

Browse files
author
Trol
committed
[DRAFT] Add scope DSL (problem)
Signed-off-by: Trol <[email protected]>
1 parent f410298 commit 450e9df

File tree

6 files changed

+79
-10
lines changed

6 files changed

+79
-10
lines changed

kotlinx-coroutines-core/api/kotlinx-coroutines-core.api

+9-3
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,8 @@ public final class kotlinx/coroutines/CancellableContinuationKt {
8787
}
8888

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

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

111+
public abstract interface class kotlinx/coroutines/CodeScope {
112+
}
113+
111114
public abstract interface class kotlinx/coroutines/CompletableDeferred : kotlinx/coroutines/Deferred {
112115
public abstract fun complete (Ljava/lang/Object;)Z
113116
public abstract fun completeExceptionally (Ljava/lang/Throwable;)Z
@@ -207,7 +210,7 @@ public final class kotlinx/coroutines/CoroutineName : kotlin/coroutines/Abstract
207210
public final class kotlinx/coroutines/CoroutineName$Key : kotlin/coroutines/CoroutineContext$Key {
208211
}
209212

210-
public abstract interface class kotlinx/coroutines/CoroutineScope {
213+
public abstract interface class kotlinx/coroutines/CoroutineScope : kotlinx/coroutines/CodeScope {
211214
public abstract fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext;
212215
}
213216

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

339+
public abstract interface class kotlinx/coroutines/InterruptibleScope : kotlinx/coroutines/CodeScope {
340+
}
341+
336342
public abstract interface class kotlinx/coroutines/Job : kotlin/coroutines/CoroutineContext$Element {
337343
public static final field Key Lkotlinx/coroutines/Job$Key;
338344
public abstract fun attachChild (Lkotlinx/coroutines/ChildJob;)Lkotlinx/coroutines/ChildHandle;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.coroutines
6+
7+
/**
8+
* Interface for code scopes that prevents scope code from accessing outer code scope methods (using outer code
9+
* scopes as implicit receivers).
10+
*
11+
* Example (on JVM):
12+
* ```
13+
* runBlocking {
14+
* // receiver `this` is an [CoroutineScope]
15+
*
16+
* runInterruptible {
17+
* // receiver `this` is an [InterruptibleScope]
18+
*
19+
* launch { /* ... */ } // <- Should not run in the scope of runBlocking. And won't compile.
20+
* queue.take()
21+
* }
22+
* }
23+
* ```
24+
*/
25+
@InternalCoroutinesApi
26+
@CodeScopeDsl
27+
public interface CodeScope
28+
29+
/**
30+
* DslMarker for CodeScope.
31+
*/
32+
@Retention(AnnotationRetention.BINARY)
33+
@Target(AnnotationTarget.CLASS)
34+
@DslMarker
35+
internal annotation class CodeScopeDsl

kotlinx-coroutines-core/common/src/CoroutineScope.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ import kotlin.coroutines.intrinsics.*
5959
* }
6060
* ```
6161
*/
62-
public interface CoroutineScope {
62+
public interface CoroutineScope: CodeScope {
6363
/**
6464
* The context of this scope.
6565
* Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.

kotlinx-coroutines-core/common/test/channels/ProduceTest.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ class ProduceTest : TestBase() {
151151
launch {
152152
expect(2)
153153
assertFailsWith<IllegalStateException> {
154-
awaitClose { expectUnreached() }
154+
awaitClose { expectUnreached() } // <- access outer ProducerScope from launch scope, won't compile
155155
expectUnreached()
156156
}
157157
}

kotlinx-coroutines-core/jvm/src/CancellationPoint.kt

+14-4
Original file line numberDiff line numberDiff line change
@@ -51,23 +51,31 @@ import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
5151
*/
5252
public suspend fun <T> runInterruptible(
5353
context: CoroutineContext = EmptyCoroutineContext,
54-
block: () -> T
54+
block: InterruptibleScope.() -> T
5555
): T {
5656
// fast path: empty context
5757
if (context === EmptyCoroutineContext) { return runInterruptibleInExpectedContext(block) }
5858
// slow path:
5959
return withContext(context) { runInterruptibleInExpectedContext(block) }
6060
}
6161

62-
private suspend fun <T> runInterruptibleInExpectedContext(block: () -> T): T =
62+
/**
63+
* Defines a scope for new interruptible blocking code blocks.
64+
*
65+
* Currently, The main goal is to restrict access to outer code scope methods (using outer scopes as
66+
* implicit receivers). See [CodeScope] for details.
67+
*/
68+
public interface InterruptibleScope: CodeScope
69+
70+
private suspend fun <T> runInterruptibleInExpectedContext(block: InterruptibleScope.() -> T): T =
6371
suspendCoroutineUninterceptedOrReturn sc@{ uCont ->
6472
try {
6573
// fast path: no job
66-
val job = uCont.context[Job] ?: return@sc block()
74+
val job = uCont.context[Job] ?: return@sc DefaultInterruptibleScope.block()
6775
// slow path
6876
val threadState = ThreadState().apply { initInterrupt(job) }
6977
try {
70-
block()
78+
DefaultInterruptibleScope.block()
7179
} finally {
7280
threadState.clearInterrupt()
7381
}
@@ -76,6 +84,8 @@ private suspend fun <T> runInterruptibleInExpectedContext(block: () -> T): T =
7684
}
7785
}
7886

87+
private object DefaultInterruptibleScope : InterruptibleScope
88+
7989
private class ThreadState {
8090

8191
fun initInterrupt(job: Job) {

kotlinx-coroutines-core/jvm/test/InterruptibleCancellationPointTest.kt

+19-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ import java.io.IOException
88
import java.util.concurrent.Executors
99
import java.util.concurrent.atomic.AtomicBoolean
1010
import java.util.concurrent.atomic.AtomicInteger
11-
import kotlin.test.*
11+
import kotlin.test.assertEquals
12+
import kotlin.test.assertFalse
13+
import kotlin.test.assertNotEquals
14+
import kotlin.test.fail
15+
import kotlin.test.Test
1216

1317
class InterruptibleCancellationPointTest: TestBase() {
1418

@@ -165,6 +169,20 @@ class InterruptibleCancellationPointTest: TestBase() {
165169
}
166170
}
167171

172+
@Test
173+
fun testCodeScopeDsl() {
174+
runBlocking {
175+
with("OtherScope") {
176+
runInterruptible {
177+
doSomethingUsefulBlocking(1, 0) // works as normal
178+
179+
// launch {} // using outer code scopes, won't compile.
180+
substring(0) // using other outer scopes, works as normal
181+
}
182+
}
183+
}
184+
}
185+
168186
private fun doSomethingUsefulBlocking(timeUseMillis: Long, result: Int): Int {
169187
Thread.sleep(timeUseMillis)
170188
return result

0 commit comments

Comments
 (0)