@@ -252,7 +252,17 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0
252252 */
253253 private fun tryFinalizeStateActually (expect : Incomplete , update : Any? , mode : Int ): Boolean {
254254 require(update !is Incomplete ) // only incomplete -> completed transition is allowed
255- if (! _state .compareAndSet(expect, update)) return false // failed
255+
256+ /*
257+ * We're publishing CompletedExceptionally as OpDescriptor to avoid races with parent:
258+ * Job can't report exception before CAS (as it can fail), but after CAS there is a small window
259+ * where the parent is considering this job (child) completed, though child has not yet reported its exception.
260+ */
261+ val updateValue = if (update is CompletedExceptionally ) HandleExceptionOp (update) else update
262+ if (! _state .compareAndSet(expect, updateValue)) return false // failed
263+ if (updateValue is HandleExceptionOp ) {
264+ updateValue.perform(this ) // help perform
265+ }
256266 completeStateFinalization(expect, update, mode)
257267 return true
258268 }
@@ -262,28 +272,22 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0
262272 * Now the job in THE FINAL state. We need to properly handle the resulting state.
263273 * Order of various invocations here is important.
264274 *
265- * 1) Standalone coroutines (launch/job) cancel their parent.
266- */
267- if (update is CompletedExceptionally ) {
268- handleJobException(update.cause)
269- }
270- /*
271- * 2) Unregister from parent job.
275+ * 1) Unregister from parent job.
272276 */
273277 parentHandle?.let {
274278 it.dispose() // volatile read parentHandle _after_ state was updated
275279 parentHandle = NonDisposableHandle // release it just in case, to aid GC
276280 }
277281 val exceptionally = update as ? CompletedExceptionally
278282 /*
279- * 3 ) Invoke onCancellationInternal: exception handling, resource cancellation etc.
283+ * 2 ) Invoke onCancellationInternal: exception handling, resource cancellation etc.
280284 * Only notify on cancellation once (expect.isCancelling)
281285 */
282286 if (! expect.isCancelling) {
283287 onCancellationInternal(exceptionally)
284288 }
285289 /*
286- * 4 ) Invoke completion handlers: .join(), callbacks etc.
290+ * 3 ) Invoke completion handlers: .join(), callbacks etc.
287291 * It's important to invoke them only AFTER exception handling, see #208
288292 */
289293 val cause = exceptionally?.cause
@@ -297,7 +301,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0
297301 expect.list?.notifyCompletion(cause)
298302 }
299303 /*
300- * 5 ) Invoke onCompletionInternal: onNext(), timeout deregistration etc.
304+ * 4 ) Invoke onCompletionInternal: onNext(), timeout deregistration etc.
301305 * It should be last so all callbacks observe consistent state
302306 * of the job which doesn't depend on callback scheduling.
303307 */
@@ -1013,6 +1017,18 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0
10131017 else
10141018 block.startCoroutineCancellable(state as T , select.completion)
10151019 }
1020+
1021+ class HandleExceptionOp (val original : CompletedExceptionally ) : OpDescriptor() {
1022+
1023+ override fun perform (affected : Any? ): Any? {
1024+ val job = (affected as JobSupport )
1025+ if (job._state .compareAndSet(this , original)) {
1026+ job.handleJobException(original.cause)
1027+ }
1028+
1029+ return null
1030+ }
1031+ }
10161032}
10171033
10181034// --------------- helper classes & constants for job implementation
0 commit comments