@@ -60,7 +60,7 @@ import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn
60
60
*
61
61
* A job can be _cancelled_ at any time with [cancel] function that forces it to transition to
62
62
* _cancelling_ state immediately. Job that is not backed by a coroutine and does not have
63
- * [ children][attachChild] becomes _cancelled_ on [cancel] immediately.
63
+ * children becomes _cancelled_ on [cancel] immediately.
64
64
* Otherwise, job becomes _cancelled_ when it finishes executing its code and
65
65
* when all its children [complete][isCompleted].
66
66
*
@@ -83,7 +83,7 @@ import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn
83
83
* the coroutine is suspended on a _cancellable_ suspension point by throwing [CancellationException].
84
84
*
85
85
* A job can have a _parent_ job. A job with a parent is cancelled when its parent is cancelled or completes exceptionally.
86
- * Parent job waits for all its [ children][attachChild] to complete in _completing_ or _cancelling_ state.
86
+ * Parent job waits for all its children to complete in _completing_ or _cancelling_ state.
87
87
* _Completing_ state is purely internal to the job. For an outside observer a _completing_ job is still active,
88
88
* while internally it is waiting for its children.
89
89
*
@@ -117,15 +117,15 @@ public interface Job : CoroutineContext.Element {
117
117
118
118
/* *
119
119
* Returns `true` when this job is active -- it was already started and has not completed or cancelled yet.
120
- * The job that is waiting for its [ children][attachChild] to complete is still considered to be active if it
120
+ * The job that is waiting for its children to complete is still considered to be active if it
121
121
* was not cancelled.
122
122
*/
123
123
public val isActive: Boolean
124
124
125
125
/* *
126
126
* Returns `true` when this job has completed for any reason. A job that was cancelled and has
127
127
* finished its execution is also considered complete. Job becomes complete only after
128
- * all its [ children][attachChild] complete.
128
+ * all its children complete.
129
129
*/
130
130
public val isCompleted: Boolean
131
131
@@ -190,17 +190,20 @@ public interface Job : CoroutineContext.Element {
190
190
* * Parent cannot complete until all its children are complete. Parent waits for all its children to
191
191
* complete in _completing_ or _cancelling_ state.
192
192
*
193
- * A child must store the resulting [DisposableHandle] and [dispose][DisposableHandle.dispose] the attachment
194
- * to its parent on its own completion.
193
+ * ** A child must store the resulting [DisposableHandle] and [dispose][DisposableHandle.dispose] the attachment
194
+ * to its parent on its own completion.**
195
195
*
196
196
* Coroutine builders and job factory functions that accept `parent` [CoroutineContext] parameter
197
197
* lookup a [Job] instance in the parent context and use this function to attach themselves as a child.
198
198
* They also store a reference to the resulting [DisposableHandle] and dispose a handle when they complete.
199
+ *
200
+ * @suppress This is an internal API. This method is too error prone for public API.
199
201
*/
202
+ @Deprecated(message = " Start child coroutine with 'parent' parameter" , level = DeprecationLevel .WARNING )
200
203
public fun attachChild (child : Job ): DisposableHandle
201
204
202
205
/* *
203
- * Cancels all [ children][attachChild] jobs of this coroutine with the given [cause]. Unlike [cancel],
206
+ * Cancels all children jobs of this coroutine with the given [cause]. Unlike [cancel],
204
207
* the state of this job itself is not affected.
205
208
*/
206
209
public fun cancelChildren (cause : Throwable ? = null)
@@ -212,7 +215,7 @@ public interface Job : CoroutineContext.Element {
212
215
* when the job is complete for any reason and the [Job] of the invoking coroutine is still [active][isActive].
213
216
* This function also [starts][Job.start] the corresponding coroutine if the [Job] was still in _new_ state.
214
217
*
215
- * Note, that the job becomes complete only when all its [ children][attachChild] are complete.
218
+ * Note, that the job becomes complete only when all its children are complete.
216
219
*
217
220
* This suspending function is cancellable and **always** checks for the cancellation of invoking coroutine's Job.
218
221
* If the [Job] of the invoking coroutine is cancelled or completed when this
@@ -274,6 +277,12 @@ public interface Job : CoroutineContext.Element {
274
277
275
278
// ------------ unstable internal API ------------
276
279
280
+ /* *
281
+ * @return `true` when Job was not complete and handler was installed, `false` otherwise (and handler is not invoked).
282
+ * @suppress **This is unstable API and it is subject to change.**
283
+ */
284
+ public fun invokeOnCompletionIfNotComplete (handler : CompletionHandler ): Boolean
285
+
277
286
/* *
278
287
* @suppress **Error**: Operator '+' on two Job objects is meaningless.
279
288
* Job is a coroutine context element and `+` is a set-sum operator for coroutine contexts.
@@ -568,7 +577,10 @@ public open class JobSupport(active: Boolean) : Job, SelectClause0, SelectClause
568
577
val handle = parent.attachChild(this )
569
578
parentHandle = handle
570
579
// now check our state _after_ registering (see updateState order of actions)
571
- if (isCompleted) handle.dispose()
580
+ if (isCompleted) {
581
+ handle.dispose()
582
+ parentHandle = NonDisposableHandle // release it just in case, to aid GC
583
+ }
572
584
}
573
585
574
586
// ------------ state query ------------
@@ -647,7 +659,10 @@ public open class JobSupport(active: Boolean) : Job, SelectClause0, SelectClause
647
659
require(update !is Incomplete ) // only incomplete -> completed transition is allowed
648
660
if (! _state .compareAndSet(expect, update)) return false
649
661
// Unregister from parent job
650
- parentHandle?.dispose() // volatile read parentHandle _after_ state was updated
662
+ parentHandle?.let {
663
+ it.dispose() // volatile read parentHandle _after_ state was updated
664
+ parentHandle = NonDisposableHandle // release it just in case, to aid GC
665
+ }
651
666
return true // continues in completeUpdateState
652
667
}
653
668
@@ -763,16 +778,19 @@ public open class JobSupport(active: Boolean) : Job, SelectClause0, SelectClause
763
778
764
779
@Suppress(" OverridingDeprecatedMember" )
765
780
public final override fun invokeOnCompletion (handler : CompletionHandler ): DisposableHandle =
766
- installHandler(handler, onCancelling = false )
781
+ invokeOnCompletion( false , handler )
767
782
768
783
@Suppress(" OverridingDeprecatedMember" )
769
784
public final override fun invokeOnCompletion (handler : CompletionHandler , onCancelling : Boolean ): DisposableHandle =
770
- installHandler(handler, onCancelling = onCancelling && hasCancellingState )
785
+ invokeOnCompletion(onCancelling, handler )
771
786
772
787
public final override fun invokeOnCompletion (onCancelling : Boolean , handler : CompletionHandler ): DisposableHandle =
773
- installHandler(handler, onCancelling = onCancelling && hasCancellingState)
788
+ installHandler(handler, onCancelling = onCancelling && hasCancellingState, invokeNow = true ) ? : NonDisposableHandle
789
+
790
+ override fun invokeOnCompletionIfNotComplete (handler : CompletionHandler ): Boolean =
791
+ installHandler(handler, false , invokeNow = false ) != null
774
792
775
- private fun installHandler (handler : CompletionHandler , onCancelling : Boolean ): DisposableHandle {
793
+ private fun installHandler (handler : CompletionHandler , onCancelling : Boolean , invokeNow : Boolean ): DisposableHandle ? {
776
794
var nodeCache: JobNode <* >? = null
777
795
loopOnState { state ->
778
796
when (state) {
@@ -791,16 +809,16 @@ public open class JobSupport(active: Boolean) : Job, SelectClause0, SelectClause
791
809
} else {
792
810
if (state is Finishing && state.cancelled != null && onCancelling) {
793
811
// installing cancellation handler on job that is being cancelled
794
- handler(( state as ? CompletedExceptionally )?.exception )
795
- return NonDisposableHandle
812
+ if (invokeNow) handler(state.cancelled.cause )
813
+ return null
796
814
}
797
815
val node = nodeCache ? : makeNode(handler, onCancelling).also { nodeCache = it }
798
816
if (addLastAtomic(state, list, node)) return node
799
817
}
800
818
}
801
819
else -> { // is complete
802
- handler((state as ? CompletedExceptionally )?.exception )
803
- return NonDisposableHandle
820
+ if (invokeNow) handler((state as ? CompletedExceptionally )?.cause )
821
+ return null
804
822
}
805
823
}
806
824
}
@@ -988,8 +1006,10 @@ public open class JobSupport(active: Boolean) : Job, SelectClause0, SelectClause
988
1006
// switch to completing state
989
1007
val completing = Finishing (state.list!! , (state as ? Finishing )?.cancelled, true )
990
1008
if (_state .compareAndSet(state, completing)) {
991
- waitForChild(child, proposedUpdate)
992
- return false
1009
+ if (tryWaitForChild(child, proposedUpdate))
1010
+ return false
1011
+ if (updateState(completing, proposedUpdate, MODE_ATOMIC_DEFAULT ))
1012
+ return true
993
1013
}
994
1014
}
995
1015
}
@@ -1005,20 +1025,24 @@ public open class JobSupport(active: Boolean) : Job, SelectClause0, SelectClause
1005
1025
private fun firstChild (state : Incomplete ) =
1006
1026
state as ? Child ? : state.list?.nextChild()
1007
1027
1008
- private fun waitForChild (child : Child , proposedUpdate : Any? ) {
1009
- child.childJob.invokeOnCompletion(handler = ChildCompletion (this , child, proposedUpdate))
1028
+ // return false when there is no more incomplete children to wait
1029
+ private tailrec fun tryWaitForChild (child : Child , proposedUpdate : Any? ): Boolean {
1030
+ if (child.childJob.invokeOnCompletionIfNotComplete(ChildCompletion (this , child, proposedUpdate)))
1031
+ return true
1032
+ val nextChild = child.nextChild() ? : return false
1033
+ return tryWaitForChild(nextChild, proposedUpdate)
1010
1034
}
1011
1035
1012
1036
internal fun continueCompleting (lastChild : Child , proposedUpdate : Any? ) {
1013
1037
loopOnState { state ->
1014
1038
if (state !is Finishing )
1015
1039
throw IllegalStateException (" Job $this is found in expected state while completing with $proposedUpdate " , proposedUpdate.exceptionOrNull)
1016
1040
// figure out if we need to wait for next child
1017
- val waitChild = lastChild.nextChild() ? : // or else no more children
1018
- if (updateState(state, proposedUpdate, MODE_ATOMIC_DEFAULT )) return else return @loopOnState
1019
- // wait for next child
1020
- waitForChild(waitChild, proposedUpdate)
1021
- return
1041
+ val waitChild = lastChild.nextChild()
1042
+ // try wait for next child
1043
+ if (waitChild != null && tryWaitForChild(waitChild, proposedUpdate)) return // waiting for next child
1044
+ // no more children to wait -- try update state
1045
+ if (updateState(state, proposedUpdate, MODE_ATOMIC_DEFAULT )) return
1022
1046
}
1023
1047
}
1024
1048
0 commit comments