Skip to content

Commit 3120530

Browse files
authored
Introduce Job.parent API (#3384)
* Introduce Job.parent API * The API is crucial for debugger extension and deadlock detection * It enables allows more fluent coroutines hierarchy inspection, e.g. capability to build a list of roots and procrss them top-bottom separately Fixes #3201
1 parent 5c582ee commit 3120530

File tree

7 files changed

+41
-4
lines changed

7 files changed

+41
-4
lines changed

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

+3
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ public abstract interface class kotlinx/coroutines/Job : kotlin/coroutines/Corou
360360
public abstract fun getCancellationException ()Ljava/util/concurrent/CancellationException;
361361
public abstract fun getChildren ()Lkotlin/sequences/Sequence;
362362
public abstract fun getOnJoin ()Lkotlinx/coroutines/selects/SelectClause0;
363+
public abstract fun getParent ()Lkotlinx/coroutines/Job;
363364
public abstract fun invokeOnCompletion (Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/DisposableHandle;
364365
public abstract fun invokeOnCompletion (ZZLkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/DisposableHandle;
365366
public abstract fun isActive ()Z
@@ -443,6 +444,7 @@ public class kotlinx/coroutines/JobSupport : kotlinx/coroutines/ChildJob, kotlin
443444
public final fun getCompletionExceptionOrNull ()Ljava/lang/Throwable;
444445
public final fun getKey ()Lkotlin/coroutines/CoroutineContext$Key;
445446
public final fun getOnJoin ()Lkotlinx/coroutines/selects/SelectClause0;
447+
public fun getParent ()Lkotlinx/coroutines/Job;
446448
protected fun handleJobException (Ljava/lang/Throwable;)Z
447449
protected final fun initParentJob (Lkotlinx/coroutines/Job;)V
448450
public final fun invokeOnCompletion (Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/DisposableHandle;
@@ -485,6 +487,7 @@ public final class kotlinx/coroutines/NonCancellable : kotlin/coroutines/Abstrac
485487
public fun getCancellationException ()Ljava/util/concurrent/CancellationException;
486488
public fun getChildren ()Lkotlin/sequences/Sequence;
487489
public fun getOnJoin ()Lkotlinx/coroutines/selects/SelectClause0;
490+
public fun getParent ()Lkotlinx/coroutines/Job;
488491
public fun invokeOnCompletion (Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/DisposableHandle;
489492
public fun invokeOnCompletion (ZZLkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/DisposableHandle;
490493
public fun isActive ()Z

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

+17-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import kotlin.jvm.*
3131
* It is completed by calling [CompletableJob.complete].
3232
*
3333
* Conceptually, an execution of a job does not produce a result value. Jobs are launched solely for their
34-
* side-effects. See [Deferred] interface for a job that produces a result.
34+
* side effects. See [Deferred] interface for a job that produces a result.
3535
*
3636
* ### Job states
3737
*
@@ -117,6 +117,22 @@ public interface Job : CoroutineContext.Element {
117117

118118
// ------------ state query ------------
119119

120+
/**
121+
* Returns the parent of the current job if the parent-child relationship
122+
* is established or `null` if the job has no parent or was successfully completed.
123+
*
124+
* Accesses to this property are not idempotent, the property becomes `null` as soon
125+
* as the job is transitioned to its final state, whether it is cancelled or completed,
126+
* and all job children are completed.
127+
*
128+
* For a coroutine, its corresponding job completes as soon as the coroutine itself
129+
* and all its children are complete.
130+
*
131+
* @see [Job] state transitions for additional details.
132+
*/
133+
@ExperimentalCoroutinesApi
134+
public val parent: Job?
135+
120136
/**
121137
* Returns `true` when this job is active -- it was already started and has not completed nor was cancelled yet.
122138
* The job that is waiting for its [children] to complete is still considered to be active if it

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

+3
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
132132
get() = _parentHandle.value
133133
set(value) { _parentHandle.value = value }
134134

135+
override val parent: Job?
136+
get() = parentHandle?.parent
137+
135138
// ------------ initialization ------------
136139

137140
/**

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

+8
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ public object NonCancellable : AbstractCoroutineContextElement(Job), Job {
2929

3030
private const val message = "NonCancellable can be used only as an argument for 'withContext', direct usages of its API are prohibited"
3131

32+
/**
33+
* Always returns `null`.
34+
* @suppress **This an internal API and should not be used from general code.**
35+
*/
36+
@Deprecated(level = DeprecationLevel.WARNING, message = message)
37+
override val parent: Job?
38+
get() = null
39+
3240
/**
3341
* Always returns `true`.
3442
* @suppress **This an internal API and should not be used from general code.**

kotlinx-coroutines-core/common/src/internal/Scopes.kt

-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ internal open class ScopeCoroutine<in T>(
2121
final override fun getStackTraceElement(): StackTraceElement? = null
2222

2323
final override val isScopedCoroutine: Boolean get() = true
24-
internal val parent: Job? get() = parentHandle?.parent
2524

2625
override fun afterCompletion(state: Any?) {
2726
// Resume in a cancellable way by default when resuming from another context

kotlinx-coroutines-core/common/test/JobStatesTest.kt

+6-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class JobStatesTest : TestBase() {
1616
@Test
1717
public fun testNormalCompletion() = runTest {
1818
expect(1)
19+
val parent = coroutineContext.job
1920
val job = launch(start = CoroutineStart.LAZY) {
2021
expect(2)
2122
// launches child
@@ -28,23 +29,27 @@ class JobStatesTest : TestBase() {
2829
assertFalse(job.isActive)
2930
assertFalse(job.isCompleted)
3031
assertFalse(job.isCancelled)
32+
assertSame(parent, job.parent)
3133
// New -> Active
3234
job.start()
3335
assertTrue(job.isActive)
3436
assertFalse(job.isCompleted)
3537
assertFalse(job.isCancelled)
38+
assertSame(parent, job.parent)
3639
// Active -> Completing
3740
yield() // scheduled & starts child
3841
expect(3)
3942
assertTrue(job.isActive)
4043
assertFalse(job.isCompleted)
4144
assertFalse(job.isCancelled)
45+
assertSame(parent, job.parent)
4246
// Completing -> Completed
4347
yield()
4448
finish(5)
4549
assertFalse(job.isActive)
4650
assertTrue(job.isCompleted)
4751
assertFalse(job.isCancelled)
52+
assertNull(job.parent)
4853
}
4954

5055
@Test
@@ -159,4 +164,4 @@ class JobStatesTest : TestBase() {
159164
assertTrue(job.isCompleted)
160165
assertTrue(job.isCancelled)
161166
}
162-
}
167+
}

kotlinx-coroutines-core/common/test/JobTest.kt

+4-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class JobTest : TestBase() {
1212
@Test
1313
fun testState() {
1414
val job = Job()
15+
assertNull(job.parent)
1516
assertTrue(job.isActive)
1617
job.cancel()
1718
assertTrue(!job.isActive)
@@ -210,11 +211,13 @@ class JobTest : TestBase() {
210211

211212
@Test
212213
fun testIncompleteJobState() = runTest {
214+
val parent = coroutineContext.job
213215
val job = launch {
214216
coroutineContext[Job]!!.invokeOnCompletion { }
215217
}
216-
218+
assertSame(parent, job.parent)
217219
job.join()
220+
assertNull(job.parent)
218221
assertTrue(job.isCompleted)
219222
assertFalse(job.isActive)
220223
assertFalse(job.isCancelled)

0 commit comments

Comments
 (0)