@@ -118,7 +118,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
118118 ------ completion listeners are not admitted anymore, invokeOnCompletion returns NonDisposableHandle
119119 + parentHandle.dispose
120120 + notifyCompletion (invoke all completion listeners)
121- + onCompletionInternal / onCompleted / onCompletedExceptionally
121+ + onCompletionInternal / onCompleted / onCancelled
122122
123123 ---------------------------------------------------------------------------------
124124 */
@@ -193,22 +193,20 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
193193 // ## IMPORTANT INVARIANT: Only one thread can be concurrently invoking this method.
194194 private fun tryFinalizeFinishingState (state : Finishing , proposedUpdate : Any? , mode : Int ): Boolean {
195195 /*
196- * Note: proposed state can be Incompleted , e.g.
196+ * Note: proposed state can be Incomplete , e.g.
197197 * async {
198- * smth .invokeOnCompletion {} // <- returns handle which implements Incomplete under the hood
198+ * something .invokeOnCompletion {} // <- returns handle which implements Incomplete under the hood
199199 * }
200200 */
201201 require(this .state == = state) // consistency check -- it cannot change
202202 require(! state.isSealed) // consistency check -- cannot be sealed yet
203203 require(state.isCompleting) // consistency check -- must be marked as completing
204204 val proposedException = (proposedUpdate as ? CompletedExceptionally )?.cause
205205 // Create the final exception and seal the state so that no more exceptions can be added
206- var suppressed = false
207206 val finalException = synchronized(state) {
208207 val exceptions = state.sealLocked(proposedException)
209208 val finalCause = getFinalRootCause(state, exceptions)
210- // Report suppressed exceptions if initial cause doesn't match final cause (due to JCE unwrapping)
211- if (finalCause != null ) suppressed = suppressExceptions(finalCause, exceptions) || finalCause != = state.rootCause
209+ if (finalCause != null ) addSuppressedExceptions(finalCause, exceptions)
212210 finalCause
213211 }
214212 // Create the final state object
@@ -222,13 +220,13 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
222220 }
223221 // Now handle the final exception
224222 if (finalException != null ) {
225- val handledByParent = cancelParent(finalException)
226- handleJobException(finalException, handledByParent )
223+ val handled = cancelParent(finalException) || handleJobException (finalException)
224+ if (handled) (finalState as CompletedExceptionally ).makeHandled( )
227225 }
228226 // Then CAS to completed state -> it must succeed
229227 require(_state .compareAndSet(state, finalState.boxIncomplete())) { " Unexpected state: ${_state .value} , expected: $state , update: $finalState " }
230228 // And process all post-completion actions
231- completeStateFinalization(state, finalState, mode, suppressed )
229+ completeStateFinalization(state, finalState, mode)
232230 return true
233231 }
234232
@@ -243,31 +241,28 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
243241 return exceptions.firstOrNull { it !is CancellationException } ? : exceptions[0 ]
244242 }
245243
246- private fun suppressExceptions (rootCause : Throwable , exceptions : List <Throwable >): Boolean {
247- if (exceptions.size <= 1 ) return false // nothing more to do here
244+ private fun addSuppressedExceptions (rootCause : Throwable , exceptions : List <Throwable >) {
245+ if (exceptions.size <= 1 ) return // nothing more to do here
248246 val seenExceptions = identitySet<Throwable >(exceptions.size)
249- var suppressed = false
250247 for (exception in exceptions) {
251248 val unwrapped = unwrap(exception)
252249 if (unwrapped != = rootCause && unwrapped !is CancellationException && seenExceptions.add(unwrapped)) {
253250 rootCause.addSuppressedThrowable(unwrapped)
254- suppressed = true
255251 }
256252 }
257- return suppressed
258253 }
259254
260255 // fast-path method to finalize normally completed coroutines without children
261256 private fun tryFinalizeSimpleState (state : Incomplete , update : Any? , mode : Int ): Boolean {
262257 check(state is Empty || state is JobNode <* >) // only simple state without lists where children can concurrently add
263258 check(update !is CompletedExceptionally ) // only for normal completion
264259 if (! _state .compareAndSet(state, update.boxIncomplete())) return false
265- completeStateFinalization(state, update, mode, false )
260+ completeStateFinalization(state, update, mode)
266261 return true
267262 }
268263
269264 // suppressed == true when any exceptions were suppressed while building the final completion cause
270- private fun completeStateFinalization (state : Incomplete , update : Any? , mode : Int , suppressed : Boolean ) {
265+ private fun completeStateFinalization (state : Incomplete , update : Any? , mode : Int ) {
271266 /*
272267 * Now the job in THE FINAL state. We need to properly handle the resulting state.
273268 * Order of various invocations here is important.
@@ -303,7 +298,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
303298 * It should be last so all callbacks observe consistent state
304299 * of the job which doesn't depend on callback scheduling.
305300 */
306- onCompletionInternal(update, mode, suppressed )
301+ onCompletionInternal(update, mode)
307302 }
308303
309304 private fun notifyCancelling (list : NodeList , cause : Throwable ) {
@@ -387,18 +382,21 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
387382 * [cancel] cause, [CancellationException] or **`null` if this job had completed normally**.
388383 * This function throws [IllegalStateException] when invoked for an job that has not [completed][isCompleted] nor
389384 * is being cancelled yet.
390- *
391- * @suppress **This is unstable API and it is subject to change.**
392385 */
393- protected fun getCompletionCause () : Throwable ? = loopOnState { state ->
394- return when (state) {
386+ protected val completionCause : Throwable ?
387+ get() = when (val state = state) {
395388 is Finishing -> state.rootCause
396389 ? : error(" Job is still new or active: $this " )
397390 is Incomplete -> error(" Job is still new or active: $this " )
398391 is CompletedExceptionally -> state.cause
399392 else -> null
400393 }
401- }
394+
395+ /* *
396+ * Returns `true` when [completionCause] exception was handled by parent coroutine.
397+ */
398+ protected val completionCauseHandled: Boolean
399+ get() = state.let { it is CompletedExceptionally && it.handled }
402400
403401 @Suppress(" OverridingDeprecatedMember" )
404402 public final override fun invokeOnCompletion (handler : CompletionHandler ): DisposableHandle =
@@ -859,8 +857,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
859857 }
860858
861859 public final override val children: Sequence <Job > get() = sequence {
862- val state = this @JobSupport.state
863- when (state) {
860+ when (val state = this @JobSupport.state) {
864861 is ChildHandleNode -> yield (state.childJob)
865862 is Incomplete -> state.list?.let { list ->
866863 list.forEach<ChildHandleNode > { yield (it.childJob) }
@@ -885,6 +882,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
885882 /* *
886883 * Override to process any exceptions that were encountered while invoking completion handlers
887884 * installed via [invokeOnCompletion].
885+ *
888886 * @suppress **This is unstable API and it is subject to change.**
889887 */
890888 internal open fun handleOnCompletionException (exception : Throwable ) {
@@ -910,7 +908,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
910908 protected open val cancelsParent: Boolean get() = true
911909
912910 /* *
913- * Returns `true` for jobs that handle their exceptions via [handleJobException] or integrate them
911+ * Returns `true` for jobs that handle their exceptions or integrate them
914912 * into the job's result via [onCompletionInternal]. The only instance of the [Job] that does not
915913 * handle its exceptions is [JobImpl] and its subclass [SupervisorJobImpl].
916914 *
@@ -919,17 +917,18 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
919917 protected open val handlesException: Boolean get() = true
920918
921919 /* *
922- * Handles the final job [exception] after it was reported to the by the parent,
923- * where [handled] is `true` when parent had already handled exception and `false` otherwise.
920+ * Handles the final job [exception] that was not handled by the parent coroutine.
921+ * Returns `true` if it handles exception (so handling at later stages is not needed).
922+ * It is designed to be overridden by launch-like coroutines
923+ * (`StandaloneCoroutine` and `ActorCoroutine`) that don't have a result type
924+ * that can represent exceptions.
924925 *
925926 * This method is invoked **exactly once** when the final exception of the job is determined
926927 * and before it becomes complete. At the moment of invocation the job and all its children are complete.
927928 *
928- * Note, [handled] is always `true` when [exception] is [CancellationException].
929- *
930929 * @suppress **This is unstable API and it is subject to change.*
931930 */
932- protected open fun handleJobException (exception : Throwable , handled : Boolean ) {}
931+ protected open fun handleJobException (exception : Throwable ) : Boolean = false
933932
934933 private fun cancelParent (cause : Throwable ): Boolean {
935934 // CancellationException is considered "normal" and parent is not cancelled when child produces it.
@@ -944,10 +943,10 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
944943 * Override for post-completion actions that need to do something with the state.
945944 * @param state the final state.
946945 * @param mode completion mode.
947- * @param suppressed true when any exceptions were suppressed while building the final completion cause.
946+ *
948947 * @suppress **This is unstable API and it is subject to change.**
949948 */
950- internal open fun onCompletionInternal (state : Any? , mode : Int , suppressed : Boolean ) {}
949+ internal open fun onCompletionInternal (state : Any? , mode : Int ) {}
951950
952951 // for nicer debugging
953952 public override fun toString (): String =
@@ -1015,8 +1014,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
10151014 return
10161015 }
10171016 if (exception == = rootCause) return // nothing to do
1018- val eh = _exceptionsHolder // volatile read
1019- when (eh) {
1017+ when (val eh = _exceptionsHolder ) { // volatile read
10201018 null -> _exceptionsHolder = exception
10211019 is Throwable -> {
10221020 if (exception == = eh) return // nothing to do
0 commit comments