@@ -456,86 +456,51 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
456
456
// Create node upfront -- for common cases it just initializes JobNode.job field,
457
457
// for user-defined handlers it allocates a JobNode object that we might not need, but this is Ok.
458
458
val node: JobNode = makeNode(handler, onCancelling)
459
- /*
460
- * LinkedList algorithm does not support removing and re-adding the same node,
461
- * so here we check whether the node is already added to the list to avoid adding the node twice.
462
- */
463
- var isNodeAdded = false
464
459
loopOnState { state ->
465
460
when (state) {
466
461
is Empty -> { // EMPTY_X state -- no completion handlers
467
462
if (state.isActive) {
468
463
// try move to SINGLE state
469
464
if (_state .compareAndSet(state, node)) return node
470
- } else {
465
+ } else
471
466
promoteEmptyToNodeList(state) // that way we can add listener for non-active coroutine
472
- }
473
467
}
474
468
is Incomplete -> {
475
469
val list = state.list
476
470
if (list == null ) { // SINGLE/SINGLE+
477
471
promoteSingleToNodeList(state as JobNode )
478
- return @loopOnState // retry
479
- }
480
- // ...else {, but without nesting
481
- var rootCause: Throwable ? = null
482
- var handle: DisposableHandle = NonDisposableHandle
483
- if (onCancelling && state is Finishing ) {
484
- synchronized(state) {
485
- // check if we are installing cancellation handler on job that is being cancelled
486
- rootCause = state.rootCause // != null if cancelling job
487
- // We add node to the list in two cases --- either the job is not being cancelled
488
- // or we are adding a child to a coroutine that is not completing yet
489
- if (rootCause == null || handler.isHandlerOf<ChildHandleNode >() && ! state.isCompleting) {
490
- // Note: add node the list while holding lock on state (make sure it cannot change)
491
- if (! isNodeAdded) list.addLast(node)
492
- if (this .state != = state) {
493
- /*
494
- * Here we have an additional check for ChildCompletion. Invoking the handler once is not enough
495
- * for this particular kind of node -- the caller makes a decision based on whether the node was added
496
- * or not and that decision should be made **once**.
497
- * To be more precise, the caller of ChildCompletion, in case when it's the last child,
498
- * should make a decision whether to start transition to the final state, based on
499
- * whether the ChildCompletion was added to the list or not. If not -- the JobNode.invoke will do that.
500
- * See comment to JobNode.invoke, we cannot differentiate the situation when external state updater
501
- * invoked or skipped the node, thus we additionally synchronize on 'markInvoked'.
502
- */
503
- if (node is ChildCompletion && ! node.markInvoked()) return node
504
- // The state can only change to Complete here, so the node can stay in the list, just retry
505
- return @loopOnState
472
+ } else {
473
+ var rootCause: Throwable ? = null
474
+ var handle: DisposableHandle = NonDisposableHandle
475
+ if (onCancelling && state is Finishing ) {
476
+ synchronized(state) {
477
+ // check if we are installing cancellation handler on job that is being cancelled
478
+ rootCause = state.rootCause // != null if cancelling job
479
+ // We add node to the list in two cases --- either the job is not being cancelled
480
+ // or we are adding a child to a coroutine that is not completing yet
481
+ if (rootCause == null || handler.isHandlerOf<ChildHandleNode >() && ! state.isCompleting) {
482
+ // Note: add node the list while holding lock on state (make sure it cannot change)
483
+ if (! addLastAtomic(state, list, node)) return @loopOnState // retry
484
+ // just return node if we don't have to invoke handler (not cancelling yet)
485
+ if (rootCause == null ) return node
486
+ // otherwise handler is invoked immediately out of the synchronized section & handle returned
487
+ handle = node
506
488
}
507
- // just return node if we don't have to invoke handler (not cancelling yet)
508
- if (rootCause == null ) return node
509
- // otherwise handler is invoked immediately out of the synchronized section & handle returned
510
- handle = node
511
489
}
512
490
}
513
- }
514
- if (rootCause != null ) {
515
- // Note: attachChild uses invokeImmediately, so it gets invoked when adding to cancelled job
516
- if (invokeImmediately) {
517
- node.invoke(rootCause)
518
- }
519
- return handle
520
- } else {
521
- if (! isNodeAdded) list.addLast(node)
522
- if (this .state != = state) {
523
- // Here again, we try to prevent concurrent finalization of the parent,
524
- // if the parent fails to add ChildCompletion because the child changed it's state to Completed.
525
- if (node is ChildCompletion && ! node.markInvoked()) return node
526
- // If the state has changed to Complete, the node can stay in the list, just retry.
527
- if (this .state !is Incomplete ) return @loopOnState
528
- // If the state is Incomplete, set the flag that the node is already added to the list instead of removing it.
529
- isNodeAdded = true
491
+ if (rootCause != null ) {
492
+ // Note: attachChild uses invokeImmediately, so it gets invoked when adding to cancelled job
493
+ if (invokeImmediately) handler.invokeIt(rootCause)
494
+ return handle
530
495
} else {
531
- return node
496
+ if (addLastAtomic(state, list, node)) return node
532
497
}
533
498
}
534
499
}
535
500
else -> { // is complete
536
- if (invokeImmediately) {
537
- node.invoke((state as ? CompletedExceptionally )?.cause)
538
- }
501
+ // :KLUDGE: We have to invoke a handler in platform-specific way via `invokeIt` extension,
502
+ // because we play type tricks on Kotlin/JS and handler is not necessarily a function there
503
+ if (invokeImmediately) handler.invokeIt((state as ? CompletedExceptionally )?.cause)
539
504
return NonDisposableHandle
540
505
}
541
506
}
@@ -555,6 +520,9 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
555
520
return node
556
521
}
557
522
523
+ private fun addLastAtomic (expect : Any , list : NodeList , node : JobNode ) =
524
+ list.addLastIf(node) { this .state == = expect }
525
+
558
526
private fun promoteEmptyToNodeList (state : Empty ) {
559
527
// try to promote it to LIST state with the corresponding state
560
528
val list = NodeList ()
@@ -628,13 +596,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
628
596
}
629
597
is Incomplete -> { // may have a list of completion handlers
630
598
// remove node from the list if there is a list
631
- if (state.list != null ) {
632
- if (! node.remove() && node.isRemoved) {
633
- // Note: .remove() returns 'false' if the node wasn't added at all, e.g.
634
- // because it was an optimized "single element list"
635
- node.helpRemove()
636
- }
637
- }
599
+ if (state.list != null ) node.remove()
638
600
return
639
601
}
640
602
else -> return // it is complete and does not have any completion handlers
@@ -852,7 +814,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
852
814
}
853
815
}
854
816
}
855
- }
817
+ }
856
818
857
819
/* *
858
820
* Completes this job. Used by [AbstractCoroutine.resume].
@@ -1189,11 +1151,9 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
1189
1151
private val child : ChildHandleNode ,
1190
1152
private val proposedUpdate : Any?
1191
1153
) : JobNode() {
1192
- override fun invokeOnce (cause : Throwable ? ) {
1154
+ override fun invoke (cause : Throwable ? ) {
1193
1155
parent.continueCompleting(state, child, proposedUpdate)
1194
1156
}
1195
- override fun toString (): String =
1196
- " ChildCompletion[$child , $proposedUpdate ]"
1197
1157
}
1198
1158
1199
1159
private class AwaitContinuation <T >(
@@ -1395,31 +1355,6 @@ internal abstract class JobNode : CompletionHandlerBase(), DisposableHandle, Inc
1395
1355
override val isActive: Boolean get() = true
1396
1356
override val list: NodeList ? get() = null
1397
1357
override fun dispose () = job.removeNode(this )
1398
- private val isInvoked = atomic(false )
1399
-
1400
- /*
1401
- * This method is guaranteed to be invoked once per JobNode lifecycle.
1402
- */
1403
- protected abstract fun invokeOnce (cause : Throwable ? )
1404
-
1405
- /*
1406
- * This method can be invoked more than once thus it's protected
1407
- * with atomic flag.
1408
- *
1409
- * It can be invoked twice via [invokeOnCompletion(invokeImmediately = true)]
1410
- * when addLastIf fails due to the race with Job state update.
1411
- * In that case, we cannot distinguish the situation when the node was added
1412
- * and the external state updater actually invoked it from the situation
1413
- * when the state updater already invoked all elements from a list and then
1414
- * we added a new node to already abandoned list.
1415
- * So, when addLastIf fails, we invoke handler in-place, using
1416
- * markInvoked as protection against the former case.
1417
- */
1418
- final override fun invoke (cause : Throwable ? ) {
1419
- if (! markInvoked()) return
1420
- invokeOnce(cause)
1421
- }
1422
- fun markInvoked () = isInvoked.compareAndSet(false , true )
1423
1358
override fun toString () = " $classSimpleName @$hexAddress [job@${job.hexAddress} ]"
1424
1359
}
1425
1360
@@ -1452,22 +1387,20 @@ internal class InactiveNodeList(
1452
1387
1453
1388
private class InvokeOnCompletion (
1454
1389
private val handler : CompletionHandler
1455
- ) : JobNode() {
1456
- override fun invokeOnce (cause : Throwable ? ) = handler.invoke(cause)
1457
- override fun toString () = " InvokeOnCompletion[$classSimpleName @$hexAddress ]"
1390
+ ) : JobNode() {
1391
+ override fun invoke (cause : Throwable ? ) = handler.invoke(cause)
1458
1392
}
1459
1393
1460
1394
private class ResumeOnCompletion (
1461
1395
private val continuation : Continuation <Unit >
1462
1396
) : JobNode() {
1463
- override fun invokeOnce (cause : Throwable ? ) = continuation.resume(Unit )
1464
- override fun toString () = " ResumeOnCompletion[$continuation ]"
1397
+ override fun invoke (cause : Throwable ? ) = continuation.resume(Unit )
1465
1398
}
1466
1399
1467
1400
private class ResumeAwaitOnCompletion <T >(
1468
1401
private val continuation : CancellableContinuationImpl <T >
1469
1402
) : JobNode() {
1470
- override fun invokeOnce (cause : Throwable ? ) {
1403
+ override fun invoke (cause : Throwable ? ) {
1471
1404
val state = job.state
1472
1405
assert { state !is Incomplete }
1473
1406
if (state is CompletedExceptionally ) {
@@ -1479,36 +1412,32 @@ private class ResumeAwaitOnCompletion<T>(
1479
1412
continuation.resume(state.unboxState() as T )
1480
1413
}
1481
1414
}
1482
- override fun toString () = " ResumeAwaitOnCompletion[$continuation ]"
1483
1415
}
1484
1416
1485
1417
internal class DisposeOnCompletion (
1486
1418
private val handle : DisposableHandle
1487
1419
) : JobNode() {
1488
- override fun invokeOnce (cause : Throwable ? ) = handle.dispose()
1489
- override fun toString (): String = " DisposeOnCompletion[${handle} ]"
1420
+ override fun invoke (cause : Throwable ? ) = handle.dispose()
1490
1421
}
1491
1422
1492
1423
private class SelectJoinOnCompletion <R >(
1493
1424
private val select : SelectInstance <R >,
1494
1425
private val block : suspend () -> R
1495
1426
) : JobNode() {
1496
- override fun invokeOnce (cause : Throwable ? ) {
1427
+ override fun invoke (cause : Throwable ? ) {
1497
1428
if (select.trySelect())
1498
1429
block.startCoroutineCancellable(select.completion)
1499
1430
}
1500
- override fun toString (): String = " SelectJoinOnCompletion[$select ]"
1501
1431
}
1502
1432
1503
1433
private class SelectAwaitOnCompletion <T , R >(
1504
1434
private val select : SelectInstance <R >,
1505
1435
private val block : suspend (T ) -> R
1506
1436
) : JobNode() {
1507
- override fun invokeOnce (cause : Throwable ? ) {
1437
+ override fun invoke (cause : Throwable ? ) {
1508
1438
if (select.trySelect())
1509
1439
job.selectAwaitCompletion(select, block)
1510
1440
}
1511
- override fun toString (): String = " SelectAwaitOnCompletion[$select ]"
1512
1441
}
1513
1442
1514
1443
// -------- invokeOnCancellation nodes
@@ -1521,28 +1450,28 @@ internal abstract class JobCancellingNode : JobNode()
1521
1450
1522
1451
private class InvokeOnCancelling (
1523
1452
private val handler : CompletionHandler
1524
- ) : JobCancellingNode() {
1453
+ ) : JobCancellingNode() {
1525
1454
// delegate handler shall be invoked at most once, so here is an additional flag
1526
- override fun invokeOnce (cause : Throwable ? ) = handler.invoke(cause)
1527
- override fun toString () = " InvokeOnCancelling[$classSimpleName @$hexAddress ]"
1455
+ private val _invoked = atomic(0 ) // todo: replace with atomic boolean after migration to recent atomicFu
1456
+ override fun invoke (cause : Throwable ? ) {
1457
+ if (_invoked .compareAndSet(0 , 1 )) handler.invoke(cause)
1458
+ }
1528
1459
}
1529
1460
1530
1461
internal class ChildHandleNode (
1531
1462
@JvmField val childJob : ChildJob
1532
1463
) : JobCancellingNode(), ChildHandle {
1533
1464
override val parent: Job get() = job
1534
- override fun invokeOnce (cause : Throwable ? ) = childJob.parentCancelled(job)
1465
+ override fun invoke (cause : Throwable ? ) = childJob.parentCancelled(job)
1535
1466
override fun childCancelled (cause : Throwable ): Boolean = job.childCancelled(cause)
1536
- override fun toString (): String = " ChildHandle[$childJob ]"
1537
1467
}
1538
1468
1539
1469
// Same as ChildHandleNode, but for cancellable continuation
1540
1470
internal class ChildContinuation (
1541
1471
@JvmField val child : CancellableContinuationImpl <* >
1542
1472
) : JobCancellingNode() {
1543
- override fun invokeOnce (cause : Throwable ? ) =
1473
+ override fun invoke (cause : Throwable ? ) {
1544
1474
child.parentCancelled(child.getContinuationCancellationCause(job))
1545
- override fun toString (): String =
1546
- " ChildContinuation[$child ]"
1475
+ }
1547
1476
}
1548
1477
0 commit comments