Structured concurrency implementation:
* Introducing async, launch, produce, actor and broadcast extensions on CoroutineScope
* Deprecate top-level coroutine builders
* Introducing currentScope and coroutineScope for manipulation with CoroutineScope interface
* Introducing CoroutineScope factories
* Introducing extension CoroutineScope.isActive
Fixes #410
diff --git a/common/kotlinx-coroutines-core-common/test/AsyncTest.kt b/common/kotlinx-coroutines-core-common/test/AsyncTest.kt
index de7e004..0496904 100644
--- a/common/kotlinx-coroutines-core-common/test/AsyncTest.kt
+++ b/common/kotlinx-coroutines-core-common/test/AsyncTest.kt
@@ -6,7 +6,6 @@
package kotlinx.coroutines.experimental
-import kotlin.coroutines.experimental.*
import kotlin.test.*
class AsyncTest : TestBase() {
@@ -221,5 +220,22 @@
finish(2)
}
+ @Test
+ fun testOverriddenParent() = runTest {
+ val parent = Job()
+ val deferred = async(parent, CoroutineStart.ATOMIC) {
+ expect(2)
+ delay(Long.MAX_VALUE)
+ }
+
+ parent.cancel()
+ try {
+ expect(1)
+ deferred.await()
+ } catch (e: JobCancellationException) {
+ finish(3)
+ }
+ }
+
private class TestException : Exception()
}
diff --git a/common/kotlinx-coroutines-core-common/test/CoroutineScopeTest.kt b/common/kotlinx-coroutines-core-common/test/CoroutineScopeTest.kt
new file mode 100644
index 0000000..22652f2
--- /dev/null
+++ b/common/kotlinx-coroutines-core-common/test/CoroutineScopeTest.kt
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.experimental
+
+import kotlin.test.*
+
+class CoroutineScopeTest : TestBase() {
+
+ @Test
+ fun testScope() = runTest {
+ suspend fun callJobScoped() = coroutineScope {
+ expect(2)
+
+ launch {
+ expect(4)
+ }
+
+ launch {
+ expect(5)
+
+ launch {
+ expect(7)
+ }
+
+ expect(6)
+
+ }
+
+ expect(3)
+ 42
+ }
+
+
+ expect(1)
+ val result = callJobScoped()
+ assertEquals(42, result)
+ yield() // Check we're not cancelled
+ finish(8)
+ }
+
+ @Test
+ fun testScopeCancelledFromWithin() = runTest {
+ expect(1)
+ suspend fun callJobScoped() = coroutineScope {
+
+ launch {
+ expect(2)
+ delay(Long.MAX_VALUE)
+ }
+
+ launch {
+ expect(3)
+ throw IllegalArgumentException()
+ }
+ }
+
+ try {
+ callJobScoped()
+ expectUnreached()
+ } catch (e: IllegalArgumentException) {
+ expect(4)
+ }
+
+ yield() // Check we're not cancelled
+ finish(5)
+ }
+
+ @Test
+ fun testScopeBlockThrows() = runTest {
+ expect(1)
+ suspend fun callJobScoped(): Unit = coroutineScope {
+
+ launch {
+ expect(2)
+ delay(Long.MAX_VALUE)
+ }
+
+ yield() // let launch sleep
+ throw NotImplementedError()
+ }
+
+ try {
+ callJobScoped()
+ expectUnreached()
+ } catch (e: NotImplementedError) {
+ expect(3)
+ }
+
+ yield() // Check we're not cancelled
+ finish(4)
+ }
+
+ @Test
+ fun testOuterJobIsCancelled() = runTest {
+
+ suspend fun callJobScoped() = coroutineScope {
+
+ launch {
+ expect(3)
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ expect(4)
+ }
+ }
+
+ expect(2)
+ delay(Long.MAX_VALUE)
+ 42
+ }
+
+
+ val outerJob = launch(coroutineContext.minusKey(Job)) {
+ expect(1)
+ try {
+ callJobScoped()
+ expectUnreached()
+ } catch (e: JobCancellationException) {
+ expect(5)
+ assertNull(e.cause)
+ }
+ }
+
+ repeat(3) { yield() } // let everything to start properly
+ outerJob.cancel()
+ outerJob.join()
+ finish(6)
+ }
+
+ @Test
+ @Suppress("UNREACHABLE_CODE")
+ fun testDocumentationExample() = runTest {
+ suspend fun loadData() = coroutineScope {
+ expect(1)
+ val data = async {
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ expect(3)
+ }
+ }
+
+ yield()
+
+ // UI updater
+ withContext(coroutineContext) {
+ expect(2)
+ throw AssertionError()
+ data.await() // Actually unreached
+ expectUnreached()
+ }
+ }
+
+
+ try {
+ loadData()
+ expectUnreached()
+ } catch (e: AssertionError) {
+ finish(4)
+ }
+ }
+}
diff --git a/common/kotlinx-coroutines-core-common/test/CoroutinesTest.kt b/common/kotlinx-coroutines-core-common/test/CoroutinesTest.kt
index 70d89ca..283a6d4 100644
--- a/common/kotlinx-coroutines-core-common/test/CoroutinesTest.kt
+++ b/common/kotlinx-coroutines-core-common/test/CoroutinesTest.kt
@@ -6,7 +6,6 @@
package kotlinx.coroutines.experimental
-import kotlin.coroutines.experimental.*
import kotlin.test.*
class CoroutinesTest : TestBase() {
diff --git a/common/kotlinx-coroutines-core-common/test/CurrentScopeTest.kt b/common/kotlinx-coroutines-core-common/test/CurrentScopeTest.kt
new file mode 100644
index 0000000..db17064
--- /dev/null
+++ b/common/kotlinx-coroutines-core-common/test/CurrentScopeTest.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("NAMED_ARGUMENTS_NOT_ALLOWED") // KT-21913
+
+package kotlinx.coroutines.experimental
+
+import kotlin.test.*
+
+class CurrentScopeTest : TestBase() {
+
+ @Test
+ fun testScope() = runTest {
+ suspend fun callJobScoped() = currentScope {
+ launch {
+ finish(3)
+ }
+ }
+
+
+ expect(1)
+ callJobScoped()
+ expect(2)
+ }
+
+ @Test
+ fun testNestedScope() = runTest {
+ suspend fun callJobScoped() = currentScope {
+ launch {
+ expect(2)
+ }
+ }
+
+ expect(1)
+ coroutineScope {
+ callJobScoped()
+ }
+
+ finish(3)
+ }
+
+ @Test
+ fun testThrowException() = runTest(expected = { it is IndexOutOfBoundsException }) {
+ suspend fun callJobScoped() = currentScope {
+ launch {
+ finish(3)
+ throw IndexOutOfBoundsException()
+ }
+ }
+
+ expect(1)
+ callJobScoped()
+ expect(2)
+ }
+}
diff --git a/common/kotlinx-coroutines-core-common/test/JobTest.kt b/common/kotlinx-coroutines-core-common/test/JobTest.kt
index 1beccb8..f83a893 100644
--- a/common/kotlinx-coroutines-core-common/test/JobTest.kt
+++ b/common/kotlinx-coroutines-core-common/test/JobTest.kt
@@ -4,7 +4,6 @@
package kotlinx.coroutines.experimental
-import kotlin.coroutines.experimental.*
import kotlin.test.*
class JobTest : TestBase() {
@@ -175,4 +174,18 @@
job.cancelAndJoin()
finish(4)
}
+
+ @Test
+ fun testOverriddenParent() = runTest {
+ val parent = Job()
+ val deferred = launch(parent, CoroutineStart.ATOMIC) {
+ expect(2)
+ delay(Long.MAX_VALUE)
+ }
+
+ parent.cancel()
+ expect(1)
+ deferred.join()
+ finish(3)
+ }
}
diff --git a/common/kotlinx-coroutines-core-common/test/WithContextTest.kt b/common/kotlinx-coroutines-core-common/test/WithContextTest.kt
index 9e40c1b..bed85d9 100644
--- a/common/kotlinx-coroutines-core-common/test/WithContextTest.kt
+++ b/common/kotlinx-coroutines-core-common/test/WithContextTest.kt
@@ -145,8 +145,7 @@
try {
withContext(job + wrapperDispatcher(coroutineContext), CoroutineStart.ATOMIC) {
- require(isActive)
- // but start atomically
+ require(!isActive) // but it had still started, because atomically
expect(2)
yield() // but will cancel here
expectUnreached()
@@ -165,7 +164,7 @@
val job = Job()
job.cancel() // try to cancel before it has a chance to run
withContext(job + wrapperDispatcher(coroutineContext), CoroutineStart.UNDISPATCHED) { // but start atomically
- require(isActive)
+ require(!isActive) // but it had still started, because undispatched
finish(2)
yield() // but will cancel here
expectUnreached()
diff --git a/common/kotlinx-coroutines-core-common/test/channels/BroadcastTest.kt b/common/kotlinx-coroutines-core-common/test/channels/BroadcastTest.kt
index 0f0d059..74bdb44 100644
--- a/common/kotlinx-coroutines-core-common/test/channels/BroadcastTest.kt
+++ b/common/kotlinx-coroutines-core-common/test/channels/BroadcastTest.kt
@@ -12,7 +12,7 @@
@Test
fun testBroadcastBasic() = runTest {
expect(1)
- val b = broadcast(coroutineContext) {
+ val b = broadcast {
expect(4)
send(1) // goes to receiver
expect(5)
diff --git a/common/kotlinx-coroutines-core-common/test/channels/ProduceTest.kt b/common/kotlinx-coroutines-core-common/test/channels/ProduceTest.kt
index 70252a7..40b4e5b 100644
--- a/common/kotlinx-coroutines-core-common/test/channels/ProduceTest.kt
+++ b/common/kotlinx-coroutines-core-common/test/channels/ProduceTest.kt
@@ -12,7 +12,7 @@
@Test
fun testBasic() = runTest {
- val c = produce(coroutineContext) {
+ val c = produce {
expect(2)
send(1)
expect(3)
@@ -30,7 +30,7 @@
@Test
fun testCancelWithoutCause() = runTest {
- val c = produce(coroutineContext) {
+ val c = produce {
expect(2)
send(1)
expect(3)
@@ -54,7 +54,7 @@
@Test
fun testCancelWithCause() = runTest {
- val c = produce(coroutineContext) {
+ val c = produce {
expect(2)
send(1)
expect(3)
@@ -92,7 +92,7 @@
cancelOnCompletion(coroutineContext)
}
- private suspend fun cancelOnCompletion(coroutineContext: CoroutineContext) {
+ private suspend fun cancelOnCompletion(coroutineContext: CoroutineContext) = currentScope {
val source = Channel<Int>()
expect(1)
val produced = produce<Int>(coroutineContext, onCompletion = source.consumes()) {