Skip to content

Commit e8f694e

Browse files
committed
Optional parent job parameter for coroutine builders
1 parent e19ee04 commit e8f694e

File tree

22 files changed

+252
-57
lines changed

22 files changed

+252
-57
lines changed

core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn
3131
* See [CoroutineDispatcher] for the standard context implementations that are provided by `kotlinx.coroutines`.
3232
* The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
3333
* in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine.
34+
* The parent job may be also explicitly specified using [parent] parameter.
35+
*
3436
* If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [DefaultDispatcher] is used.
3537
*
3638
* By default, the coroutine is immediately scheduled for execution.
@@ -44,32 +46,43 @@ import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn
4446
* the context of another coroutine, then any uncaught exception leads to the cancellation of parent coroutine.
4547
*
4648
* See [newCoroutineContext] for a description of debugging facilities that are available for newly created coroutine.
47-
49+
*
4850
* @param context context of the coroutine. The default value is [DefaultDispatcher].
4951
* @param start coroutine start option. The default value is [CoroutineStart.DEFAULT].
52+
* @param parent explicitly specifies the parent job, overrides job from the [context] (if any).
5053
* @param block the coroutine code.
5154
*/
5255
public fun launch(
5356
context: CoroutineContext = DefaultDispatcher,
5457
start: CoroutineStart = CoroutineStart.DEFAULT,
58+
parent: Job? = null,
5559
block: suspend CoroutineScope.() -> Unit
5660
): Job {
57-
val newContext = newCoroutineContext(context)
61+
val newContext = newCoroutineContext(context, parent)
5862
val coroutine = if (start.isLazy)
5963
LazyStandaloneCoroutine(newContext, block) else
6064
StandaloneCoroutine(newContext, active = true)
61-
coroutine.initParentJob(context[Job])
65+
coroutine.initParentJob(newContext[Job])
6266
start(block, coroutine, coroutine)
6367
return coroutine
6468
}
6569

70+
/** @suppress **Deprecated**: Binary compatibility */
71+
@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
72+
public fun launch(
73+
context: CoroutineContext = DefaultDispatcher,
74+
start: CoroutineStart = CoroutineStart.DEFAULT,
75+
block: suspend CoroutineScope.() -> Unit
76+
): Job =
77+
launch(context, start, block = block)
78+
6679
/**
6780
* @suppress **Deprecated**: Use `start = CoroutineStart.XXX` parameter
6881
*/
6982
@Deprecated(message = "Use `start = CoroutineStart.XXX` parameter",
7083
replaceWith = ReplaceWith("launch(context, if (start) CoroutineStart.DEFAULT else CoroutineStart.LAZY, block)"))
7184
public fun launch(context: CoroutineContext, start: Boolean, block: suspend CoroutineScope.() -> Unit): Job =
72-
launch(context, if (start) CoroutineStart.DEFAULT else CoroutineStart.LAZY, block)
85+
launch(context, if (start) CoroutineStart.DEFAULT else CoroutineStart.LAZY, block = block)
7386

7487
/**
7588
* Calls the specified suspending block with a given coroutine context, suspends until it completes, and returns
@@ -147,7 +160,7 @@ public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, bl
147160
val eventLoop = if (context[ContinuationInterceptor] == null) BlockingEventLoop(currentThread) else null
148161
val newContext = newCoroutineContext(context + (eventLoop ?: EmptyCoroutineContext))
149162
val coroutine = BlockingCoroutine<T>(newContext, currentThread, privateEventLoop = eventLoop != null)
150-
coroutine.initParentJob(context[Job])
163+
coroutine.initParentJob(newContext[Job])
151164
block.startCoroutine(coroutine, coroutine)
152165
return coroutine.joinBlocking()
153166
}

core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineContext.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,12 @@ public val DefaultDispatcher: CoroutineDispatcher = CommonPool
9292
* Coroutine name can be explicitly assigned using [CoroutineName] context element.
9393
* The string "coroutine" is used as a default name.
9494
*/
95-
public fun newCoroutineContext(context: CoroutineContext): CoroutineContext {
95+
@JvmOverloads // for binary compatibility with newCoroutineContext(context: CoroutineContext) version
96+
public fun newCoroutineContext(context: CoroutineContext, parent: Job? = null): CoroutineContext {
9697
val debug = if (DEBUG) context + CoroutineId(COROUTINE_ID.incrementAndGet()) else context
98+
val wp = if (parent == null) debug else debug + parent
9799
return if (context !== DefaultDispatcher && context[ContinuationInterceptor] == null)
98-
debug + DefaultDispatcher else debug
100+
wp + DefaultDispatcher else wp
99101
}
100102

101103
/**

core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Deferred.kt

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ public interface Deferred<out T> : Job {
143143
* See [CoroutineDispatcher] for the standard context implementations that are provided by `kotlinx.coroutines`.
144144
* The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
145145
* in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine.
146+
* The parent job may be also explicitly specified using [parent] parameter.
147+
*
146148
* If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [DefaultDispatcher] is used.
147149
*
148150
* By default, the coroutine is immediately scheduled for execution.
@@ -153,29 +155,40 @@ public interface Deferred<out T> : Job {
153155
*
154156
* @param context context of the coroutine. The default value is [DefaultDispatcher].
155157
* @param start coroutine start option. The default value is [CoroutineStart.DEFAULT].
158+
* @param parent explicitly specifies the parent job, overrides job from the [context] (if any).*
156159
* @param block the coroutine code.
157160
*/
158161
public fun <T> async(
159162
context: CoroutineContext = DefaultDispatcher,
160163
start: CoroutineStart = CoroutineStart.DEFAULT,
164+
parent: Job? = null,
161165
block: suspend CoroutineScope.() -> T
162166
): Deferred<T> {
163-
val newContext = newCoroutineContext(context)
167+
val newContext = newCoroutineContext(context, parent)
164168
val coroutine = if (start.isLazy)
165169
LazyDeferredCoroutine(newContext, block) else
166170
DeferredCoroutine<T>(newContext, active = true)
167-
coroutine.initParentJob(context[Job])
171+
coroutine.initParentJob(newContext[Job])
168172
start(block, coroutine, coroutine)
169173
return coroutine
170174
}
171175

176+
/** @suppress **Deprecated**: Binary compatibility */
177+
@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
178+
public fun <T> async(
179+
context: CoroutineContext = DefaultDispatcher,
180+
start: CoroutineStart = CoroutineStart.DEFAULT,
181+
block: suspend CoroutineScope.() -> T
182+
): Deferred<T> =
183+
async(context, start, block = block)
184+
172185
/**
173186
* @suppress **Deprecated**: Use `start = CoroutineStart.XXX` parameter
174187
*/
175188
@Deprecated(message = "Use `start = CoroutineStart.XXX` parameter",
176189
replaceWith = ReplaceWith("async(context, if (start) CoroutineStart.DEFAULT else CoroutineStart.LAZY, block)"))
177190
public fun <T> async(context: CoroutineContext, start: Boolean, block: suspend CoroutineScope.() -> T): Deferred<T> =
178-
async(context, if (start) CoroutineStart.DEFAULT else CoroutineStart.LAZY, block)
191+
async(context, if (start) CoroutineStart.DEFAULT else CoroutineStart.LAZY, block = block)
179192

180193
/**
181194
* @suppress **Deprecated**: `defer` was renamed to `async`.

core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Actor.kt

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ interface ActorJob<in E> : SendChannel<E> {
6060
* See [CoroutineDispatcher] for the standard context implementations that are provided by `kotlinx.coroutines`.
6161
* The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
6262
* in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine.
63+
* The parent job may be also explicitly specified using [parent] parameter.
64+
*
6365
* If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [DefaultDispatcher] is used.
6466
*
6567
* By default, the coroutine is immediately scheduled for execution.
@@ -77,24 +79,36 @@ interface ActorJob<in E> : SendChannel<E> {
7779
* @param context context of the coroutine. The default value is [DefaultDispatcher].
7880
* @param capacity capacity of the channel's buffer (no buffer by default).
7981
* @param start coroutine start option. The default value is [CoroutineStart.DEFAULT].
82+
* @param parent explicitly specifies the parent job, overrides job from the [context] (if any).*
8083
* @param block the coroutine code.
8184
*/
8285
public fun <E> actor(
8386
context: CoroutineContext = DefaultDispatcher,
8487
capacity: Int = 0,
8588
start: CoroutineStart = CoroutineStart.DEFAULT,
89+
parent: Job? = null,
8690
block: suspend ActorScope<E>.() -> Unit
8791
): SendChannel<E> {
88-
val newContext = newCoroutineContext(context)
92+
val newContext = newCoroutineContext(context, parent)
8993
val channel = Channel<E>(capacity)
9094
val coroutine = if (start.isLazy)
9195
LazyActorCoroutine(newContext, channel, block) else
9296
ActorCoroutine(newContext, channel, active = true)
93-
coroutine.initParentJob(context[Job])
97+
coroutine.initParentJob(newContext[Job])
9498
start(block, coroutine, coroutine)
9599
return coroutine
96100
}
97101

102+
/** @suppress **Deprecated**: Binary compatibility */
103+
@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
104+
public fun <E> actor(
105+
context: CoroutineContext = DefaultDispatcher,
106+
capacity: Int = 0,
107+
start: CoroutineStart = CoroutineStart.DEFAULT,
108+
block: suspend ActorScope<E>.() -> Unit
109+
): ActorJob<E> =
110+
actor(context, capacity, start, block = block) as ActorJob<E>
111+
98112
private open class ActorCoroutine<E>(
99113
parentContext: CoroutineContext,
100114
channel: Channel<E>,

core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/channels/Produce.kt

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ public interface ProducerScope<in E> : CoroutineScope, SendChannel<E> {
3939
* @suppress **Deprecated**: Use `ReceiveChannel`.
4040
*/
4141
@Deprecated(message = "Use `ReceiveChannel`", replaceWith = ReplaceWith("ReceiveChannel"))
42-
interface ProducerJob<out E> : ReceiveChannel<E> {
42+
@Suppress("MULTIPLE_DEFAULTS_INHERITED_FROM_SUPERTYPES_WHEN_NO_EXPLICIT_OVERRIDE")
43+
interface ProducerJob<out E> : ReceiveChannel<E>, Job {
4344
@Deprecated(message = "Use ReceiveChannel itself")
4445
val channel: ReceiveChannel<E>
4546
}
@@ -59,6 +60,8 @@ interface ProducerJob<out E> : ReceiveChannel<E> {
5960
* See [CoroutineDispatcher] for the standard context implementations that are provided by `kotlinx.coroutines`.
6061
* The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
6162
* in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine.
63+
* The parent job may be also explicitly specified using [parent] parameter.
64+
*
6265
* If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [DefaultDispatcher] is used.
6366
*
6467
* Uncaught exceptions in this coroutine close the channel with this exception as a cause and
@@ -68,20 +71,32 @@ interface ProducerJob<out E> : ReceiveChannel<E> {
6871
*
6972
* @param context context of the coroutine. The default value is [DefaultDispatcher].
7073
* @param capacity capacity of the channel's buffer (no buffer by default).
74+
* @param parent explicitly specifies the parent job, overrides job from the [context] (if any).*
7175
* @param block the coroutine code.
7276
*/
7377
public fun <E> produce(
7478
context: CoroutineContext = DefaultDispatcher,
7579
capacity: Int = 0,
80+
parent: Job? = null,
7681
block: suspend ProducerScope<E>.() -> Unit
7782
): ReceiveChannel<E> {
7883
val channel = Channel<E>(capacity)
79-
return ProducerCoroutine(newCoroutineContext(context), channel).apply {
80-
initParentJob(context[Job])
81-
block.startCoroutine(this, this)
82-
}
84+
val newContext = newCoroutineContext(context, parent)
85+
val coroutine = ProducerCoroutine(newContext, channel)
86+
coroutine.initParentJob(newContext[Job])
87+
block.startCoroutine(coroutine, coroutine)
88+
return coroutine
8389
}
8490

91+
/** @suppress **Deprecated**: Binary compatibility */
92+
@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
93+
public fun <E> produce(
94+
context: CoroutineContext = DefaultDispatcher,
95+
capacity: Int = 0,
96+
block: suspend ProducerScope<E>.() -> Unit
97+
): ProducerJob<E> =
98+
produce(context, capacity, block = block) as ProducerJob<E>
99+
85100
/**
86101
* @suppress **Deprecated**: Renamed to `produce`.
87102
*/
@@ -91,7 +106,7 @@ public fun <E> buildChannel(
91106
capacity: Int = 0,
92107
block: suspend ProducerScope<E>.() -> Unit
93108
): ProducerJob<E> =
94-
produce(context, capacity, block) as ProducerJob<E>
109+
produce(context, capacity, block = block) as ProducerJob<E>
95110

96111
private class ProducerCoroutine<E>(parentContext: CoroutineContext, channel: Channel<E>) :
97112
ChannelCoroutine<E>(parentContext, channel, active = true), ProducerScope<E>, ProducerJob<E>

core/kotlinx-coroutines-core/src/test/kotlin/guide/example-context-10.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ fun main(args: Array<String>) = runBlocking<Unit> {
2424
// now launch ten coroutines for a demo, each working for a different time
2525
val coroutines = List(10) { i ->
2626
// they are all children of our job object
27-
launch(coroutineContext + job) { // we use the context of main runBlocking thread, but with our own job object
27+
launch(coroutineContext, parent = job) { // we use the context of main runBlocking thread, but with our parent job
2828
delay((i + 1) * 200L) // variable delay 200ms, 400ms, ... etc
2929
println("Coroutine $i is done")
3030
}

core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/ReaderJob.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ interface ReaderScope : CoroutineScope {
2323
fun reader(coroutineContext: CoroutineContext,
2424
channel: ByteChannel,
2525
block: suspend ReaderScope.() -> Unit): ReaderJob {
26-
val coroutine = ReaderCoroutine(newCoroutineContext(coroutineContext), channel)
27-
coroutine.initParentJob(coroutineContext[Job])
26+
val newContext = newCoroutineContext(coroutineContext)
27+
val coroutine = ReaderCoroutine(newContext, channel)
28+
coroutine.initParentJob(newContext[Job])
2829
block.startCoroutine(coroutine, coroutine)
2930
return coroutine
3031
}

core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/WriterJob.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ interface WriterScope : CoroutineScope {
2323
fun writer(coroutineContext: CoroutineContext,
2424
channel: ByteChannel,
2525
block: suspend WriterScope.() -> Unit): WriterJob {
26-
val coroutine = WriterCoroutine(newCoroutineContext(coroutineContext), channel)
27-
coroutine.initParentJob(coroutineContext[Job])
26+
val newContext = newCoroutineContext(coroutineContext)
27+
val coroutine = WriterCoroutine(newContext, channel)
28+
coroutine.initParentJob(newContext[Job])
2829
block.startCoroutine(coroutine, coroutine)
2930
return coroutine
3031
}

coroutines-guide.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1182,8 +1182,10 @@ to avoid memory leaks.
11821182

11831183
We can manage a lifecycle of our coroutines by creating an instance of [Job] that is tied to
11841184
the lifecycle of our activity. A job instance is created using [Job()] factory function
1185-
as the following example shows. We need to make sure that all the coroutines are started
1186-
with this job in their context and then a single invocation of [Job.cancel] terminates them all.
1185+
as the following example shows. For convenience, rather than using `launch(coroutineContext + job)` expression,
1186+
we can write `launch(coroutineContext, parent = job)` to make explicit the fact that the parent job is being used.
1187+
1188+
Now, a single invocation of [Job.cancel] cancels all the children we've launched.
11871189
Moreover, [Job.join] waits for all of them to complete, so we can also use [cancelAndJoin] here in
11881190
this example:
11891191

@@ -1193,7 +1195,7 @@ fun main(args: Array<String>) = runBlocking<Unit> {
11931195
// now launch ten coroutines for a demo, each working for a different time
11941196
val coroutines = List(10) { i ->
11951197
// they are all children of our job object
1196-
launch(coroutineContext + job) { // we use the context of main runBlocking thread, but with our own job object
1198+
launch(coroutineContext, parent = job) { // we use the context of main runBlocking thread, but with our parent job
11971199
delay((i + 1) * 200L) // variable delay 200ms, 400ms, ... etc
11981200
println("Coroutine $i is done")
11991201
}

integration/kotlinx-coroutines-guava/src/main/kotlin/kotlinx/coroutines/experimental/guava/ListenableFuture.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import kotlin.coroutines.experimental.CoroutineContext
3535
* See [CoroutineDispatcher] for the standard context implementations that are provided by `kotlinx.coroutines`.
3636
* The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
3737
* in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine.
38+
* The parent job may be also explicitly specified using [parent] parameter.
39+
*
3840
* If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [DefaultDispatcher] is used.
3941
*
4042
* By default, the coroutine is immediately scheduled for execution.
@@ -47,22 +49,33 @@ import kotlin.coroutines.experimental.CoroutineContext
4749
*
4850
* @param context context of the coroutine. The default value is [DefaultDispatcher].
4951
* @param start coroutine start option. The default value is [CoroutineStart.DEFAULT].
52+
* @param parent explicitly specifies the parent job, overrides job from the [context] (if any).
5053
* @param block the coroutine code.
5154
*/
5255
public fun <T> future(
5356
context: CoroutineContext = DefaultDispatcher,
5457
start: CoroutineStart = CoroutineStart.DEFAULT,
58+
parent: Job? = null,
5559
block: suspend CoroutineScope.() -> T
5660
): ListenableFuture<T> {
5761
require(!start.isLazy) { "$start start is not supported" }
58-
val newContext = newCoroutineContext(context)
62+
val newContext = newCoroutineContext(context, parent)
5963
val job = Job(newContext[Job])
6064
val future = ListenableFutureCoroutine<T>(newContext + job)
6165
job.cancelFutureOnCompletion(future)
6266
start(block, receiver=future, completion=future) // use the specified start strategy
6367
return future
6468
}
6569

70+
/** @suppress **Deprecated**: Binary compatibility */
71+
@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
72+
public fun <T> future(
73+
context: CoroutineContext = DefaultDispatcher,
74+
start: CoroutineStart = CoroutineStart.DEFAULT,
75+
block: suspend CoroutineScope.() -> T
76+
): ListenableFuture<T> =
77+
future(context, start, block = block)
78+
6679
private class ListenableFutureCoroutine<T>(
6780
override val context: CoroutineContext
6881
) : AbstractFuture<T>(), Continuation<T>, CoroutineScope {

0 commit comments

Comments
 (0)