@@ -252,7 +252,17 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0
252
252
*/
253
253
private fun tryFinalizeStateActually (expect : Incomplete , update : Any? , mode : Int ): Boolean {
254
254
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
+ }
256
266
completeStateFinalization(expect, update, mode)
257
267
return true
258
268
}
@@ -262,28 +272,22 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0
262
272
* Now the job in THE FINAL state. We need to properly handle the resulting state.
263
273
* Order of various invocations here is important.
264
274
*
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.
272
276
*/
273
277
parentHandle?.let {
274
278
it.dispose() // volatile read parentHandle _after_ state was updated
275
279
parentHandle = NonDisposableHandle // release it just in case, to aid GC
276
280
}
277
281
val exceptionally = update as ? CompletedExceptionally
278
282
/*
279
- * 3 ) Invoke onCancellationInternal: exception handling, resource cancellation etc.
283
+ * 2 ) Invoke onCancellationInternal: exception handling, resource cancellation etc.
280
284
* Only notify on cancellation once (expect.isCancelling)
281
285
*/
282
286
if (! expect.isCancelling) {
283
287
onCancellationInternal(exceptionally)
284
288
}
285
289
/*
286
- * 4 ) Invoke completion handlers: .join(), callbacks etc.
290
+ * 3 ) Invoke completion handlers: .join(), callbacks etc.
287
291
* It's important to invoke them only AFTER exception handling, see #208
288
292
*/
289
293
val cause = exceptionally?.cause
@@ -297,7 +301,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0
297
301
expect.list?.notifyCompletion(cause)
298
302
}
299
303
/*
300
- * 5 ) Invoke onCompletionInternal: onNext(), timeout deregistration etc.
304
+ * 4 ) Invoke onCompletionInternal: onNext(), timeout deregistration etc.
301
305
* It should be last so all callbacks observe consistent state
302
306
* of the job which doesn't depend on callback scheduling.
303
307
*/
@@ -1013,6 +1017,18 @@ internal open class JobSupport constructor(active: Boolean) : Job, SelectClause0
1013
1017
else
1014
1018
block.startCoroutineCancellable(state as T , select.completion)
1015
1019
}
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
+ }
1016
1032
}
1017
1033
1018
1034
// --------------- helper classes & constants for job implementation
0 commit comments