@@ -27,17 +27,23 @@ import kotlin.coroutines.experimental.CoroutineContext
27
27
// --------------- core job interfaces ---------------
28
28
29
29
/* *
30
- * A background job. It has two states: _active_ (initial state) and _completed_ (final state).
30
+ * A background job.
31
+ * A job can be _cancelled_ at any time with [cancel] function that forces it to become _completed_ immediately.
32
+ *
33
+ * It has two states:
34
+ * * _Active_ (initial state) -- [isActive] `true`,
35
+ * [getCompletionException] throws [IllegalStateException].
36
+ * * _Completed_ (final state) -- [isActive] `false`.
31
37
*
32
- * A job can be _cancelled_ at any time with [cancel] function that forces it to become completed immediately.
33
38
* A job in the coroutine [context][CoroutineScope.context] represents the coroutine itself.
34
39
* A job is active while the coroutine is working and job's cancellation aborts the coroutine when
35
40
* the coroutine is suspended on a _cancellable_ suspension point by throwing [CancellationException]
36
- * inside the coroutine.
41
+ * or the cancellation cause inside the coroutine.
37
42
*
38
43
* A job can have a _parent_. A job with a parent is cancelled when its parent completes.
39
44
*
40
- * All functions on this interface are thread-safe.
45
+ * All functions on this interface and on all interfaces derived from it are **thread-safe** and can
46
+ * be safely invoked from concurrent coroutines without external synchronization.
41
47
*/
42
48
public interface Job : CoroutineContext .Element {
43
49
/* *
@@ -56,17 +62,23 @@ public interface Job : CoroutineContext.Element {
56
62
public val isActive: Boolean
57
63
58
64
/* *
59
- * Returns [CancellationException] that [cancellable][suspendCancellableCoroutine] suspending functions throw when
60
- * trying to suspend in the context of this job. This function throws [IllegalAccessException] when invoked
61
- * for an [active][isActive] job.
65
+ * Returns the exception that signals the completion of this job -- it returns the original
66
+ * [cancel] cause or an instance of [CancellationException] if this job had completed
67
+ * normally or was cancelled without a cause. This function throws
68
+ * [IllegalStateException] when invoked for an [active][isActive] job.
69
+ *
70
+ * The [cancellable][suspendCancellableCoroutine] suspending functions throw this exception
71
+ * when trying to suspend in the context of this job.
62
72
*/
63
- fun getInactiveCancellationException (): CancellationException
73
+ fun getCompletionException (): Throwable
64
74
65
75
/* *
66
76
* Registers completion handler. The action depends on the state of this job.
67
77
* When job is cancelled with [cancel], then the handler is immediately invoked
68
- * with a cancellation reason. Otherwise, handler will be invoked once when this
69
- * job is complete (cancellation also is a form of completion).
78
+ * with a cancellation cause or with a fresh [CancellationException].
79
+ * Otherwise, handler will be invoked once when this job is complete
80
+ * (cancellation also is a form of completion).
81
+ *
70
82
* The resulting [Registration] can be used to [Registration.unregister] if this
71
83
* registration is no longer needed. There is no need to unregister after completion.
72
84
*/
@@ -75,8 +87,8 @@ public interface Job : CoroutineContext.Element {
75
87
/* *
76
88
* Cancel this activity with an optional cancellation [cause]. The result is `true` if this job was
77
89
* cancelled as a result of this invocation and `false` otherwise
78
- * (if it was already cancelled or it is [NonCancellable]).
79
- * Repeated invocation of this function has no effect and always produces `false`.
90
+ * (if it was already _completed_ or if it is [NonCancellable]).
91
+ * Repeated invocations of this function have no effect and always produce `false`.
80
92
*
81
93
* When cancellation has a clear reason in the code, an instance of [CancellationException] should be created
82
94
* at the corresponding original cancellation site and passed into this method to aid in debugging by providing
@@ -247,19 +259,19 @@ internal open class JobSupport : AbstractCoroutineContextElement(Job), Job {
247
259
248
260
fun completeUpdateState (expect : Any , update : Any? ) {
249
261
// #3. Invoke completion handlers
250
- val reason = (update as ? CompletedExceptionally )?.cancelCause
262
+ val cause = (update as ? CompletedExceptionally )?.exception
251
263
var completionException: Throwable ? = null
252
264
when (expect) {
253
265
// SINGLE/SINGLE+ state -- one completion handler (common case)
254
266
is JobNode -> try {
255
- expect.invoke(reason )
267
+ expect.invoke(cause )
256
268
} catch (ex: Throwable ) {
257
269
completionException = ex
258
270
}
259
271
// LIST state -- a list of completion handlers
260
272
is NodeList -> expect.forEach<JobNode > { node ->
261
273
try {
262
- node.invoke(reason )
274
+ node.invoke(cause )
263
275
} catch (ex: Throwable ) {
264
276
completionException?.apply { addSuppressed(ex) } ? : run { completionException = ex }
265
277
}
@@ -275,12 +287,12 @@ internal open class JobSupport : AbstractCoroutineContextElement(Job), Job {
275
287
276
288
final override val isActive: Boolean get() = state is Active
277
289
278
- override fun getInactiveCancellationException (): CancellationException {
290
+ override fun getCompletionException (): Throwable {
279
291
val state = getState()
280
292
return when (state) {
281
293
is Active -> throw IllegalStateException (" Job is still active" )
282
- is CompletedExceptionally -> state.cancellationException
283
- else -> CancellationException (" Job has completed with result " )
294
+ is CompletedExceptionally -> state.exception
295
+ else -> CancellationException (" Job has completed normally " )
284
296
}
285
297
}
286
298
@@ -311,7 +323,7 @@ internal open class JobSupport : AbstractCoroutineContextElement(Job), Job {
311
323
}
312
324
// is not active anymore
313
325
else -> {
314
- handler((state as ? Cancelled )?.cancelCause )
326
+ handler((state as ? CompletedExceptionally )?.exception )
315
327
return EmptyRegistration
316
328
}
317
329
}
@@ -379,49 +391,40 @@ internal open class JobSupport : AbstractCoroutineContextElement(Job), Job {
379
391
*/
380
392
internal interface Active
381
393
382
- private object Empty : Active
383
-
384
- private class NodeList : LockFreeLinkedListHead (), Active
385
-
386
- /* *
387
- * Abstract class for a [state][getState] of a job that had completed exceptionally, including cancellation.
388
- */
389
- internal abstract class CompletedExceptionally {
390
- abstract val cancelCause: Throwable // original reason or fresh CancellationException
391
- abstract val exception: Throwable // the exception to be thrown in continuation
394
+ private object Empty : Active {
395
+ override fun toString (): String = " Empty"
396
+ }
392
397
393
- // convert cancelCause to CancellationException on first need
394
- @Volatile
395
- private var _cancellationException : CancellationException ? = null
396
-
397
- val cancellationException : CancellationException get() =
398
- _cancellationException ? : // atomic read volatile var or else build new
399
- (cancelCause as ? CancellationException ? :
400
- CancellationException (cancelCause.message)
401
- . apply { initCause(cancelCause) } )
402
- . also { _cancellationException = it }
398
+ private class NodeList : LockFreeLinkedListHead (), Active {
399
+ override fun toString (): String = buildString {
400
+ append( " [ " )
401
+ var first = true
402
+ this @NodeList.forEach< JobNode > { node ->
403
+ if (first) first = false else append( " , " )
404
+ append(node)
405
+ }
406
+ append( " ] " )
407
+ }
403
408
}
404
409
405
410
/* *
406
- * Represents a [state][getState] of a cancelled job.
411
+ * Class for a [state][getState] of a job that had completed exceptionally, including cancellation .
407
412
*/
408
- internal class Cancelled ( specifiedCause : Throwable ? ) : CompletedExceptionally( ) {
413
+ internal open class CompletedExceptionally ( cause : Throwable ? ) {
409
414
@Volatile
410
- private var _cancelCause = specifiedCause // materialize CancellationException on first need
415
+ private var _exception : Throwable ? = cause // materialize CancellationException on first need
411
416
412
- override val cancelCause : Throwable get() =
413
- _cancelCause ? : // atomic read volatile var or else create new
414
- CancellationException (" Job was cancelled" ).also { _cancelCause = it }
417
+ val exception : Throwable get() =
418
+ _exception ? : // atomic read volatile var or else create new
419
+ CancellationException (" Job was cancelled" ).also { _exception = it }
415
420
416
- override val exception : Throwable get() = cancellationException
421
+ override fun toString (): String = " ${javaClass.simpleName} [ $exception ] "
417
422
}
418
423
419
424
/* *
420
- * Represents a [state][getState] of a failed job .
425
+ * A specific subclass of [CompletedExceptionally] for cancelled jobs .
421
426
*/
422
- internal class Failed (override val exception : Throwable ) : CompletedExceptionally() {
423
- override val cancelCause: Throwable get() = exception
424
- }
427
+ internal class Cancelled (cause : Throwable ? ) : CompletedExceptionally(cause)
425
428
}
426
429
427
430
internal abstract class JobNode (
@@ -438,7 +441,7 @@ private class InvokeOnCompletion(
438
441
val handler : CompletionHandler
439
442
) : JobNode(job) {
440
443
override fun invoke (reason : Throwable ? ) = handler.invoke(reason)
441
- override fun toString () = " InvokeOnCompletion[${handler:: class .java .name} @${Integer .toHexString(System .identityHashCode(handler))} ]"
444
+ override fun toString () = " InvokeOnCompletion[${handler.javaClass .name} @${Integer .toHexString(System .identityHashCode(handler))} ]"
442
445
}
443
446
444
447
private class ResumeOnCompletion (
0 commit comments