@@ -88,35 +88,35 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0
8888
8989 + Job object is created
9090 ## 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
9494 ## 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
9898 ## 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
106106 ## 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
111111 ## 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
120120
121121 ---------------------------------------------------------------------------------
122122 */
@@ -220,11 +220,10 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0
220220 // failed job final state
221221 else -> CompletedExceptionally (finalException)
222222 }
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)
228227 }
229228 // Then CAS to completed state -> it must succeed
230229 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
271270
272271 private fun suppressExceptions (rootCause : Throwable , exceptions : List <Throwable >): Boolean {
273272 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)
276274 var suppressed = false
277275 for (i in 1 until exceptions.size) {
278276 val unwrapped = unwrap(exceptions[i])
@@ -673,8 +671,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0
673671 loopOnState { state ->
674672 when (state) {
675673 is Finishing -> { // already finishing -- collect exceptions
676- var notifyRootCause: Throwable ? = null
677- synchronized(state) {
674+ val notifyRootCause = synchronized(state) {
678675 if (state.isSealed) return false // too late, already sealed -- cannot add exception nor mark cancelled
679676 // add exception, do nothing is parent is cancelling child that is already failing
680677 val wasFailing = state.isFailing // will notify if was not failing
@@ -686,7 +683,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0
686683 // mark as cancelling if cancel was requested
687684 if (cancel) state.isCancelling = true
688685 // take cause for notification is was not failing before
689- notifyRootCause = state.rootCause.takeIf { ! wasFailing }
686+ state.rootCause.takeIf { ! wasFailing }
690687 }
691688 notifyRootCause?.let { notifyFailing(state.list, it) }
692689 return true
@@ -774,8 +771,12 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0
774771 private fun tryMakeCompleting (state : Any? , proposedUpdate : Any? , mode : Int ): Int {
775772 if (state !is Incomplete )
776773 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+ */
779780 if ((state is Empty || state is JobNode <* >) && state !is ChildJob && proposedUpdate !is CompletedExceptionally ) {
780781 if (! tryFinalizeSimpleState(state, proposedUpdate, mode)) return COMPLETING_RETRY
781782 return COMPLETING_COMPLETED
0 commit comments