Repository: Kotlin/kotlinx.coroutines
File: kotlinx-coroutines-core/common/src/JobSupport.kt (Kotlin)

Code before:
                        promoteEmptyToNodeList(state) // that way we can add listener for non-active coroutine
                }
                is Incomplete -> {
                    val list = state.list
                    if (list == null) { // SINGLE/SINGLE+
                        promoteSingleToNodeList(state as JobNode<*>)
                    } else {
                        var rootCause: Throwable? = null
                        var handle: DisposableHandle = NonDisposableHandle
                        if (onCancelling && state is Finishing) {
                            synchronized(state) {
                                // check if we are installing cancellation handler on job that is being cancelled
                                rootCause = state.rootCause // != null if cancelling job
                                // We add node to the list in two cases --- either the job is not being cancelled
                                // or we are adding a child to a coroutine that is not completing yet
                                if (rootCause == null || handler.isHandlerOf<ChildHandleNode>() && !state.isCompleting) {
                                    // Note: add node the list while holding lock on state (make sure it cannot change)
                                    val node = nodeAdded ?: makeNode(handler, onCancelling).also { nodeAdded = it }
                                    if (!addLast(state, list, node)) {
                                        // parent failed to add ChildCompletion node to the child, the child became Completed
                                        // if ChildCompletion is already invoked, child started parent completion
                                        // return handler, so that parent would wait for the child completion
                                        if (node is ChildCompletion && !node.markInvoked()) return node
                                        return@loopOnState // retry
                                    }
                                    // just return node if we don't have to invoke handler (not cancelling yet)
                                    if (rootCause == null) return node
                                    // otherwise handler is invoked immediately out of the synchronized section & handle returned
                                    handle = node
                                }
                            }
                        }
                        if (rootCause != null) {
                            // Note: attachChild uses invokeImmediately, so it gets invoked when adding to cancelled job
                            if (invokeImmediately) {
                                if (nodeAdded != null) {
                                    nodeAdded!!.invoke(rootCause)
                                } else {
                                    handler.invokeIt(rootCause)
                                }
                            }
                            return handle
                        } else {
                            val node = nodeAdded ?: makeNode(handler, onCancelling).also { nodeAdded = it }
                            // if ChildCompletion is already invoked, return handler, so that parent would wait for the child to complete
                            if (addLast(state, list, node) || node is ChildCompletion && !node.markInvoked()) {
                                return node
                            }
                        }
                    }
                }

Review comment (suggestion):
I've moved that `ChildCompletion` logic from `addLast` impl here.

Consider this scenario:
The Parent is `Finishing` and starts to wait for a child to complete. It tries to add `ChildCompletion` node to the child:
 
if it succeeds and `ChildCompletion` is added before the child changes its state to `Completed`, then the parent just waits for the child

if the child has already completed, and there are no other children left to wait for, then the parent starts it's final transition to `Completed` state.

But as adding to the list is not atomic with the state change now, the child may change it's state to `Completed` and see the list with `ChildCompletion` added before this node is disposed, invoke `ChildCompletion` and start parent's terminal state transition to `Completed` state (`finalizeFinishingState`). While the parent reports, that the child has already completed, no one to wait for and starts it's transition to the `Completed` state itself (concurrently with the child). 

So, I check here whether `ChildCompletion` was already invoked and if so, the parent should wait for the child completion.

Code after:
        loopOnState { state ->
            when (state) {
                is Empty -> { // EMPTY_X state -- no completion handlers
                    if (state.isActive) {
                        // try move to SINGLE state
                        if (_state.compareAndSet(state, node)) return node
                    } else {
                        promoteEmptyToNodeList(state) // that way we can add listener for non-active coroutine
                    }
                }
                is Incomplete -> {
                    val list = state.list
                    if (list == null) { // SINGLE/SINGLE+
                        promoteSingleToNodeList(state as JobNode)
                        return@loopOnState // retry
                    }
                    // ...else {, but without nesting
                    var rootCause: Throwable? = null
                    var handle: DisposableHandle = NonDisposableHandle
                    if (onCancelling && state is Finishing) {
                        synchronized(state) {
                            // check if we are installing cancellation handler on job that is being cancelled
                            rootCause = state.rootCause // != null if cancelling job
                            // We add node to the list in two cases --- either the job is not being cancelled
                            // or we are adding a child to a coroutine that is not completing yet
                            if (rootCause == null || handler.isHandlerOf<ChildHandleNode>() && !state.isCompleting) {
                                // Note: add node the list while holding lock on state (make sure it cannot change)
                                if (!isNodeAdded) list.addLast(node)
                                if (this.state !== state) {
                                    /*
                                     * Here we have an additional check for ChildCompletion. Invoking the handler once is not enough
                                     * for this particular kind of node -- the caller makes a decision based on whether the node was added
                                     * or not and that decision should be made **once**.
                                     * To be more precise, the caller of ChildCompletion, in case when it's the last child,
                                     * should make a decision whether to start transition to the final state, based on
                                     * whether the ChildCompletion was added to the list or not. If not -- the JobNode.invoke will do that.
                                     * See comment to JobNode.invoke, we cannot differentiate the situation when external state updater
                                     * invoked or skipped the node, thus we additionally synchronize on 'markInvoked'.
                                     */
                                    if (node is ChildCompletion && !node.markInvoked()) return node
                                    // The state can only change to Complete here, so the node can stay in the list, just retry
                                    return@loopOnState
                                }
                                // just return node if we don't have to invoke handler (not cancelling yet)
                                if (rootCause == null) return node
                                // otherwise handler is invoked immediately out of the synchronized section & handle returned
                                handle = node
                            }
                        }
                    }
                    if (rootCause != null) {