@@ -118,7 +118,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
118
118
------ completion listeners are not admitted anymore, invokeOnCompletion returns NonDisposableHandle
119
119
+ parentHandle.dispose
120
120
+ notifyCompletion (invoke all completion listeners)
121
- + onCompletionInternal / onCompleted / onCompletedExceptionally
121
+ + onCompletionInternal / onCompleted / onCancelled
122
122
123
123
---------------------------------------------------------------------------------
124
124
*/
@@ -193,22 +193,20 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
193
193
// ## IMPORTANT INVARIANT: Only one thread can be concurrently invoking this method.
194
194
private fun tryFinalizeFinishingState (state : Finishing , proposedUpdate : Any? , mode : Int ): Boolean {
195
195
/*
196
- * Note: proposed state can be Incompleted , e.g.
196
+ * Note: proposed state can be Incomplete , e.g.
197
197
* async {
198
- * smth .invokeOnCompletion {} // <- returns handle which implements Incomplete under the hood
198
+ * something .invokeOnCompletion {} // <- returns handle which implements Incomplete under the hood
199
199
* }
200
200
*/
201
201
require(this .state == = state) // consistency check -- it cannot change
202
202
require(! state.isSealed) // consistency check -- cannot be sealed yet
203
203
require(state.isCompleting) // consistency check -- must be marked as completing
204
204
val proposedException = (proposedUpdate as ? CompletedExceptionally )?.cause
205
205
// Create the final exception and seal the state so that no more exceptions can be added
206
- var suppressed = false
207
206
val finalException = synchronized(state) {
208
207
val exceptions = state.sealLocked(proposedException)
209
208
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)
212
210
finalCause
213
211
}
214
212
// Create the final state object
@@ -222,13 +220,13 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
222
220
}
223
221
// Now handle the final exception
224
222
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( )
227
225
}
228
226
// Then CAS to completed state -> it must succeed
229
227
require(_state .compareAndSet(state, finalState.boxIncomplete())) { " Unexpected state: ${_state .value} , expected: $state , update: $finalState " }
230
228
// And process all post-completion actions
231
- completeStateFinalization(state, finalState, mode, suppressed )
229
+ completeStateFinalization(state, finalState, mode)
232
230
return true
233
231
}
234
232
@@ -243,31 +241,28 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
243
241
return exceptions.firstOrNull { it !is CancellationException } ? : exceptions[0 ]
244
242
}
245
243
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
248
246
val seenExceptions = identitySet<Throwable >(exceptions.size)
249
- var suppressed = false
250
247
for (exception in exceptions) {
251
248
val unwrapped = unwrap(exception)
252
249
if (unwrapped != = rootCause && unwrapped !is CancellationException && seenExceptions.add(unwrapped)) {
253
250
rootCause.addSuppressedThrowable(unwrapped)
254
- suppressed = true
255
251
}
256
252
}
257
- return suppressed
258
253
}
259
254
260
255
// fast-path method to finalize normally completed coroutines without children
261
256
private fun tryFinalizeSimpleState (state : Incomplete , update : Any? , mode : Int ): Boolean {
262
257
check(state is Empty || state is JobNode <* >) // only simple state without lists where children can concurrently add
263
258
check(update !is CompletedExceptionally ) // only for normal completion
264
259
if (! _state .compareAndSet(state, update.boxIncomplete())) return false
265
- completeStateFinalization(state, update, mode, false )
260
+ completeStateFinalization(state, update, mode)
266
261
return true
267
262
}
268
263
269
264
// 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 ) {
271
266
/*
272
267
* Now the job in THE FINAL state. We need to properly handle the resulting state.
273
268
* Order of various invocations here is important.
@@ -303,7 +298,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
303
298
* It should be last so all callbacks observe consistent state
304
299
* of the job which doesn't depend on callback scheduling.
305
300
*/
306
- onCompletionInternal(update, mode, suppressed )
301
+ onCompletionInternal(update, mode)
307
302
}
308
303
309
304
private fun notifyCancelling (list : NodeList , cause : Throwable ) {
@@ -387,18 +382,21 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
387
382
* [cancel] cause, [CancellationException] or **`null` if this job had completed normally**.
388
383
* This function throws [IllegalStateException] when invoked for an job that has not [completed][isCompleted] nor
389
384
* is being cancelled yet.
390
- *
391
- * @suppress **This is unstable API and it is subject to change.**
392
385
*/
393
- protected fun getCompletionCause () : Throwable ? = loopOnState { state ->
394
- return when (state) {
386
+ protected val completionCause : Throwable ?
387
+ get() = when (val state = state) {
395
388
is Finishing -> state.rootCause
396
389
? : error(" Job is still new or active: $this " )
397
390
is Incomplete -> error(" Job is still new or active: $this " )
398
391
is CompletedExceptionally -> state.cause
399
392
else -> null
400
393
}
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 }
402
400
403
401
@Suppress(" OverridingDeprecatedMember" )
404
402
public final override fun invokeOnCompletion (handler : CompletionHandler ): DisposableHandle =
@@ -859,8 +857,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
859
857
}
860
858
861
859
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) {
864
861
is ChildHandleNode -> yield (state.childJob)
865
862
is Incomplete -> state.list?.let { list ->
866
863
list.forEach<ChildHandleNode > { yield (it.childJob) }
@@ -885,6 +882,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
885
882
/* *
886
883
* Override to process any exceptions that were encountered while invoking completion handlers
887
884
* installed via [invokeOnCompletion].
885
+ *
888
886
* @suppress **This is unstable API and it is subject to change.**
889
887
*/
890
888
internal open fun handleOnCompletionException (exception : Throwable ) {
@@ -910,7 +908,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
910
908
protected open val cancelsParent: Boolean get() = true
911
909
912
910
/* *
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
914
912
* into the job's result via [onCompletionInternal]. The only instance of the [Job] that does not
915
913
* handle its exceptions is [JobImpl] and its subclass [SupervisorJobImpl].
916
914
*
@@ -919,17 +917,18 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
919
917
protected open val handlesException: Boolean get() = true
920
918
921
919
/* *
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.
924
925
*
925
926
* This method is invoked **exactly once** when the final exception of the job is determined
926
927
* and before it becomes complete. At the moment of invocation the job and all its children are complete.
927
928
*
928
- * Note, [handled] is always `true` when [exception] is [CancellationException].
929
- *
930
929
* @suppress **This is unstable API and it is subject to change.*
931
930
*/
932
- protected open fun handleJobException (exception : Throwable , handled : Boolean ) {}
931
+ protected open fun handleJobException (exception : Throwable ) : Boolean = false
933
932
934
933
private fun cancelParent (cause : Throwable ): Boolean {
935
934
// 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
944
943
* Override for post-completion actions that need to do something with the state.
945
944
* @param state the final state.
946
945
* @param mode completion mode.
947
- * @param suppressed true when any exceptions were suppressed while building the final completion cause.
946
+ *
948
947
* @suppress **This is unstable API and it is subject to change.**
949
948
*/
950
- internal open fun onCompletionInternal (state : Any? , mode : Int , suppressed : Boolean ) {}
949
+ internal open fun onCompletionInternal (state : Any? , mode : Int ) {}
951
950
952
951
// for nicer debugging
953
952
public override fun toString (): String =
@@ -1015,8 +1014,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
1015
1014
return
1016
1015
}
1017
1016
if (exception == = rootCause) return // nothing to do
1018
- val eh = _exceptionsHolder // volatile read
1019
- when (eh) {
1017
+ when (val eh = _exceptionsHolder ) { // volatile read
1020
1018
null -> _exceptionsHolder = exception
1021
1019
is Throwable -> {
1022
1020
if (exception == = eh) return // nothing to do
0 commit comments