@@ -88,35 +88,35 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0
88
88
89
89
+ Job object is created
90
90
## NEW: state == EMPTY_ACTIVE | is InactiveNodeList
91
- + initParentJob / initParentJobInternal (invokes attachChild on its parent, initializes parentHandle)
92
- ~ waits for start
93
- >> start / join / await invoked
91
+ + initParentJob / initParentJobInternal (invokes attachChild on its parent, initializes parentHandle)
92
+ ~ waits for start
93
+ >> start / join / await invoked
94
94
## ACTIVE: state == EMPTY_ACTIVE | is JobNode | is NodeList
95
- + onStartInternal / onStart (lazy coroutine is started)
96
- ~ active coroutine is working
97
- >> childFailed / fail invoked
95
+ + onStartInternal / onStart (lazy coroutine is started)
96
+ ~ active coroutine is working (or scheduled to execution)
97
+ >> childFailed / fail invoked
98
98
## FAILING: state is Finishing, state.rootCause != null
99
- ------ failing listeners are not admitted anymore, invokeOnCompletion(onFailing=true) returns NonDisposableHandle
100
- ------ new children get immediately cancelled, but are still admitted to the list
101
- + onFailing
102
- + notifyFailing (invoke all failing listeners -- cancel all children, suspended functions resume with exception)
103
- + failParent (rootCause of failure is communicated to the parent, parent starts failing, too)
104
- ~ waits for completion of coroutine body
105
- >> makeCompleting / makeCompletingOnce invoked
99
+ ------ failing listeners are not admitted anymore, invokeOnCompletion(onFailing=true) returns NonDisposableHandle
100
+ ------ new children get immediately cancelled, but are still admitted to the list
101
+ + onFailing
102
+ + notifyFailing (invoke all failing listeners -- cancel all children, suspended functions resume with exception)
103
+ + failParent (rootCause of failure is communicated to the parent, parent starts failing, too)
104
+ ~ waits for completion of coroutine body
105
+ >> makeCompleting / makeCompletingOnce invoked
106
106
## COMPLETING: state is Finishing, state.isCompleting == true
107
- ------ new children are not admitted anymore, attachChild returns NonDisposableHandle
108
- ~ waits for children
109
- >> last child completes
110
- - computes the final exception
107
+ ------ new children are not admitted anymore, attachChild returns NonDisposableHandle
108
+ ~ waits for children
109
+ >> last child completes
110
+ - computes the final exception
111
111
## SEALED: state is Finishing, state.isSealed == true
112
- ------ cancel/childFailed returns false (cannot handle exceptions anymore)
113
- + failParent (final exception is communicated to the parent, parent incorporates it)
114
- + handleJobException ("launch" StandaloneCoroutine invokes CoroutineExceptionHandler)
115
- ## COMPLETE: state !is Incomplete (CompletedExceptionally | Cancelled)
116
- ------ completion listeners are not admitted anymore, invokeOnCompletion returns NonDisposableHandle
117
- + parentHandle.dispose
118
- + notifyCompletion (invoke all completion listeners)
119
- + onCompletionInternal / onCompleted / onCompletedExceptionally
112
+ ------ cancel/childFailed returns false (cannot handle exceptions anymore)
113
+ + failParent (final exception is communicated to the parent, parent incorporates it)
114
+ + handleJobException ("launch" StandaloneCoroutine invokes CoroutineExceptionHandler)
115
+ ## COMPLETE: state !is Incomplete (CompletedExceptionally | Cancelled)
116
+ ------ completion listeners are not admitted anymore, invokeOnCompletion returns NonDisposableHandle
117
+ + parentHandle.dispose
118
+ + notifyCompletion (invoke all completion listeners)
119
+ + onCompletionInternal / onCompleted / onCompletedExceptionally
120
120
121
121
---------------------------------------------------------------------------------
122
122
*/
@@ -220,11 +220,10 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0
220
220
// failed job final state
221
221
else -> CompletedExceptionally (finalException)
222
222
}
223
- // Now handle exception
224
- if (finalException != null ) {
225
- if (! failParent(finalException)) {
226
- handleJobException(finalException)
227
- }
223
+
224
+ // Now handle exception if parent can't handle it
225
+ if (finalException != null && ! failParent(finalException)) {
226
+ handleJobException(finalException)
228
227
}
229
228
// Then CAS to completed state -> it must succeed
230
229
require(_state .compareAndSet(state, finalState)) { " Unexpected state: ${_state .value} , expected: $state , update: $finalState " }
@@ -271,8 +270,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0
271
270
272
271
private fun suppressExceptions (rootCause : Throwable , exceptions : List <Throwable >): Boolean {
273
272
if (exceptions.size <= 1 ) return false // nothing more to do here
274
- // TODO it should be identity set and optimized for small footprints
275
- val seenExceptions = HashSet <Throwable >(exceptions.size)
273
+ val seenExceptions = identitySet<Throwable >(exceptions.size)
276
274
var suppressed = false
277
275
for (i in 1 until exceptions.size) {
278
276
val unwrapped = unwrap(exceptions[i])
@@ -673,8 +671,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0
673
671
loopOnState { state ->
674
672
when (state) {
675
673
is Finishing -> { // already finishing -- collect exceptions
676
- var notifyRootCause: Throwable ? = null
677
- synchronized(state) {
674
+ val notifyRootCause = synchronized(state) {
678
675
if (state.isSealed) return false // too late, already sealed -- cannot add exception nor mark cancelled
679
676
// add exception, do nothing is parent is cancelling child that is already failing
680
677
val wasFailing = state.isFailing // will notify if was not failing
@@ -686,7 +683,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0
686
683
// mark as cancelling if cancel was requested
687
684
if (cancel) state.isCancelling = true
688
685
// take cause for notification is was not failing before
689
- notifyRootCause = state.rootCause.takeIf { ! wasFailing }
686
+ state.rootCause.takeIf { ! wasFailing }
690
687
}
691
688
notifyRootCause?.let { notifyFailing(state.list, it) }
692
689
return true
@@ -774,8 +771,12 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0
774
771
private fun tryMakeCompleting (state : Any? , proposedUpdate : Any? , mode : Int ): Int {
775
772
if (state !is Incomplete )
776
773
return COMPLETING_ALREADY_COMPLETING
777
- // FAST PATH -- no children to wait for && simple state (no list) && not failing => can complete immediately
778
- // Failures always have to go through Finishing state to serialize exception handling
774
+ /*
775
+ * FAST PATH -- no children to wait for && simple state (no list) && not failing => can complete immediately
776
+ * Failures always have to go through Finishing state to serialize exception handling.
777
+ * Otherwise, there can be a race between (completed state -> handled exception and newly attached child/join)
778
+ * which may miss unhandled exception.
779
+ */
779
780
if ((state is Empty || state is JobNode <* >) && state !is ChildJob && proposedUpdate !is CompletedExceptionally ) {
780
781
if (! tryFinalizeSimpleState(state, proposedUpdate, mode)) return COMPLETING_RETRY
781
782
return COMPLETING_COMPLETED
0 commit comments