Skip to content

Commit 5ae3947

Browse files
authored
Add ensureActive extension
Fixes #963
1 parent 08f8214 commit 5ae3947

File tree

4 files changed

+131
-0
lines changed

4 files changed

+131
-0
lines changed

binary-compatibility-validator/reference-public-api/kotlinx-coroutines-core.txt

+3
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ public final class kotlinx/coroutines/CoroutineScopeKt {
202202
public static final fun MainScope ()Lkotlinx/coroutines/CoroutineScope;
203203
public static final fun cancel (Lkotlinx/coroutines/CoroutineScope;)V
204204
public static final fun coroutineScope (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
205+
public static final fun ensureActive (Lkotlinx/coroutines/CoroutineScope;)V
205206
public static final fun isActive (Lkotlinx/coroutines/CoroutineScope;)Z
206207
public static final fun plus (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/CoroutineScope;
207208
}
@@ -350,6 +351,8 @@ public final class kotlinx/coroutines/JobKt {
350351
public static synthetic fun cancelChildren$default (Lkotlinx/coroutines/Job;Ljava/lang/Throwable;ILjava/lang/Object;)V
351352
public static final fun cancelFutureOnCancellation (Lkotlinx/coroutines/CancellableContinuation;Ljava/util/concurrent/Future;)V
352353
public static final fun cancelFutureOnCompletion (Lkotlinx/coroutines/Job;Ljava/util/concurrent/Future;)Lkotlinx/coroutines/DisposableHandle;
354+
public static final fun ensureActive (Lkotlin/coroutines/CoroutineContext;)V
355+
public static final fun ensureActive (Lkotlinx/coroutines/Job;)V
353356
public static final fun isActive (Lkotlin/coroutines/CoroutineContext;)Z
354357
}
355358

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

+16
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,19 @@ public fun CoroutineScope.cancel() {
203203
val job = coroutineContext[Job] ?: error("Scope cannot be cancelled because it does not have a job: $this")
204204
job.cancel()
205205
}
206+
207+
/**
208+
* Ensures that current scope is [active][CoroutineScope.isActive].
209+
* Throws [IllegalStateException] if the context does not have a job in it.
210+
*
211+
* If the job is no longer active, throws [CancellationException].
212+
* If the job was cancelled, thrown exception contains the original cancellation cause.
213+
*
214+
* This method is a drop-in replacement for the following code, but with more precise exception:
215+
* ```
216+
* if (!isActive) {
217+
* throw CancellationException()
218+
* }
219+
* ```
220+
*/
221+
public fun CoroutineScope.ensureActive(): Unit = coroutineContext.ensureActive()

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

+35
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,41 @@ public fun CoroutineContext.cancel(): Unit {
536536
this[Job]?.cancel()
537537
}
538538

539+
/**
540+
* Ensures that current job is [active][Job.isActive].
541+
* If the job is no longer active, throws [CancellationException].
542+
* If the job was cancelled, thrown exception contains the original cancellation cause.
543+
*
544+
* This method is a drop-in replacement for the following code, but with more precise exception:
545+
* ```
546+
* if (!job.isActive) {
547+
* throw CancellationException()
548+
* }
549+
* ```
550+
*/
551+
public fun Job.ensureActive(): Unit {
552+
if (!isActive) throw getCancellationException()
553+
}
554+
555+
/**
556+
* Ensures that job in the current context is [active][Job.isActive].
557+
* Throws [IllegalStateException] if the context does not have a job in it.
558+
*
559+
* If the job is no longer active, throws [CancellationException].
560+
* If the job was cancelled, thrown exception contains the original cancellation cause.
561+
*
562+
* This method is a drop-in replacement for the following code, but with more precise exception:
563+
* ```
564+
* if (!isActive) {
565+
* throw CancellationException()
566+
* }
567+
* ```
568+
*/
569+
public fun CoroutineContext.ensureActive(): Unit {
570+
val job = get(Job) ?: error("Context cannot be checked for liveness because it does not have a job: $this")
571+
job.ensureActive()
572+
}
573+
539574
/**
540575
* @suppress
541576
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright 2016-2019 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+
import kotlin.test.*
8+
9+
class EnsureActiveTest : TestBase() {
10+
11+
private val job = Job()
12+
private val scope = CoroutineScope(job + CoroutineExceptionHandler { _, _ -> })
13+
14+
@Test
15+
fun testIsActive() = runTest {
16+
expect(1)
17+
scope.launch(Dispatchers.Unconfined) {
18+
ensureActive()
19+
coroutineContext.ensureActive()
20+
coroutineContext[Job]!!.ensureActive()
21+
expect(2)
22+
delay(Long.MAX_VALUE)
23+
}
24+
25+
expect(3)
26+
job.ensureActive()
27+
scope.ensureActive()
28+
scope.coroutineContext.ensureActive()
29+
job.cancelAndJoin()
30+
finish(4)
31+
}
32+
33+
@Test
34+
fun testIsCompleted() = runTest {
35+
expect(1)
36+
scope.launch(Dispatchers.Unconfined) {
37+
ensureActive()
38+
coroutineContext.ensureActive()
39+
coroutineContext[Job]!!.ensureActive()
40+
expect(2)
41+
}
42+
43+
expect(3)
44+
job.complete()
45+
job.join()
46+
assertFailsWith<JobCancellationException> { job.ensureActive() }
47+
assertFailsWith<JobCancellationException> { scope.ensureActive() }
48+
assertFailsWith<JobCancellationException> { scope.coroutineContext.ensureActive() }
49+
finish(4)
50+
}
51+
52+
53+
@Test
54+
fun testIsCancelled() = runTest {
55+
expect(1)
56+
scope.launch(Dispatchers.Unconfined) {
57+
ensureActive()
58+
coroutineContext.ensureActive()
59+
coroutineContext[Job]!!.ensureActive()
60+
expect(2)
61+
throw TestException()
62+
}
63+
64+
expect(3)
65+
checkException { job.ensureActive() }
66+
checkException { scope.ensureActive() }
67+
checkException { scope.coroutineContext.ensureActive() }
68+
finish(4)
69+
}
70+
71+
private inline fun checkException(block: () -> Unit) {
72+
val result = runCatching(block)
73+
val exception = result.exceptionOrNull() ?: fail()
74+
assertTrue(exception is JobCancellationException)
75+
assertTrue(exception.cause is TestException)
76+
}
77+
}

0 commit comments

Comments
 (0)