Skip to content

Commit de0b797

Browse files
committed
Remove DCSS
1 parent 0300a4b commit de0b797

File tree

8 files changed

+98
-208
lines changed

8 files changed

+98
-208
lines changed

kotlinx-coroutines-core/common/src/JobSupport.kt

+28-13
Original file line numberDiff line numberDiff line change
@@ -160,12 +160,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
160160
* If final state of the job is [Incomplete], then it is boxed into [IncompleteStateBox]
161161
* and should be [unboxed][unboxState] before returning to user code.
162162
*/
163-
internal val state: Any? get() {
164-
_state.loop { state -> // helper loop on state (complete in-progress atomic operations)
165-
if (state !is OpDescriptor) return state
166-
state.perform(this)
167-
}
168-
}
163+
internal val state: Any? get() = _state.value
169164

170165
/**
171166
* @suppress **This is unstable API and it is subject to change.**
@@ -325,6 +320,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
325320
private fun notifyCancelling(list: NodeList, cause: Throwable) {
326321
// first cancel our own children
327322
onCancelling(cause)
323+
list.closeForSome()
328324
notifyHandlers<JobCancellingNode>(list, cause)
329325
// then cancel parent
330326
cancelParent(cause) // tentative cancellation -- does not matter if there is no parent
@@ -356,8 +352,10 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
356352
return parent.childCancelled(cause) || isCancellation
357353
}
358354

359-
private fun NodeList.notifyCompletion(cause: Throwable?) =
355+
private fun NodeList.notifyCompletion(cause: Throwable?) {
356+
close()
360357
notifyHandlers<JobNode>(this, cause)
358+
}
361359

362360
private inline fun <reified T: JobNode> notifyHandlers(list: NodeList, cause: Throwable?) {
363361
var exception: Throwable? = null
@@ -488,7 +486,11 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
488486
// or we are adding a child to a coroutine that is not completing yet
489487
if (rootCause == null || handler is ChildHandleNode && !state.isCompleting) {
490488
// Note: add node the list while holding lock on state (make sure it cannot change)
491-
if (!addLastAtomic(state, list, node)) return@loopOnState // retry
489+
if (!list.addLast(
490+
node,
491+
allowedAfterPartialClosing = handler is ChildHandleNode
492+
)
493+
) return@loopOnState // retry
492494
// just return node if we don't have to invoke handler (not cancelling yet)
493495
if (rootCause == null) return node
494496
// otherwise handler is invoked immediately out of the synchronized section & handle returned
@@ -500,8 +502,24 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
500502
// Note: attachChild uses invokeImmediately, so it gets invoked when adding to cancelled job
501503
if (invokeImmediately) handler.invoke(rootCause)
502504
return handle
503-
} else {
504-
if (addLastAtomic(state, list, node)) return node
505+
} else if (list.addLast(
506+
node, allowedAfterPartialClosing = !onCancelling || handler is ChildHandleNode
507+
)) {
508+
if (handler is ChildHandleNode) {
509+
/** Handling the following case:
510+
* - A child requested to be added to the list;
511+
* - We checked the state and saw that it wasn't `Finishing`;
512+
* - Then, the job got cancelled and notified everyone about it;
513+
* - Only then did we add the child to the list
514+
* - and ended up here.
515+
*/
516+
val latestState = this@JobSupport.state
517+
if (latestState is Finishing) {
518+
// Assumption: children always have `invokeImmediately = true`.
519+
synchronized(latestState) { latestState.rootCause }?.let { handler.invoke(it) }
520+
}
521+
}
522+
return node
505523
}
506524
}
507525
}
@@ -528,9 +546,6 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
528546
return node
529547
}
530548

531-
private fun addLastAtomic(expect: Any, list: NodeList, node: JobNode) =
532-
list.addLastIf(node) { this.state === expect }
533-
534549
private fun promoteEmptyToNodeList(state: Empty) {
535550
// try to promote it to LIST state with the corresponding state
536551
val list = NodeList()

kotlinx-coroutines-core/common/src/internal/Atomic.kt

-67
This file was deleted.

kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt

+3-5
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,16 @@
22

33
package kotlinx.coroutines.internal
44

5-
import kotlinx.coroutines.*
6-
import kotlin.jvm.*
7-
85
/** @suppress **This is unstable API and it is subject to change.** */
96
public expect open class LockFreeLinkedListNode() {
107
public val isRemoved: Boolean
118
public val nextNode: LockFreeLinkedListNode
129
public val prevNode: LockFreeLinkedListNode
13-
public fun addLast(node: LockFreeLinkedListNode)
10+
public fun addLast(node: LockFreeLinkedListNode, allowedAfterPartialClosing: Boolean): Boolean
1411
public fun addOneIfEmpty(node: LockFreeLinkedListNode): Boolean
15-
public inline fun addLastIf(node: LockFreeLinkedListNode, crossinline condition: () -> Boolean): Boolean
1612
public open fun remove(): Boolean
13+
public fun close()
14+
public fun closeForSome()
1715

1816
}
1917

kotlinx-coroutines-core/common/test/JobTest.kt

+14
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,20 @@ class JobTest : TestBase() {
174174
finish(4)
175175
}
176176

177+
@Test
178+
fun testInvokeOnCancellingFiringOnNormalExit() = runTest {
179+
val job = launch {
180+
expect(2)
181+
}
182+
job.invokeOnCompletion(onCancelling = true) {
183+
assertNull(it)
184+
expect(3)
185+
}
186+
expect(1)
187+
job.join()
188+
finish(4)
189+
}
190+
177191
@Test
178192
fun testOverriddenParent() = runTest {
179193
val parent = Job()

kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt

+21-70
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,6 @@ import kotlin.jvm.*
88

99
private typealias Node = LockFreeLinkedListNode
1010

11-
@PublishedApi
12-
internal const val UNDECIDED: Int = 0
13-
14-
@PublishedApi
15-
internal const val SUCCESS: Int = 1
16-
17-
@PublishedApi
18-
internal const val FAILURE: Int = 2
19-
20-
@PublishedApi
21-
internal val CONDITION_FALSE: Any = Symbol("CONDITION_FALSE")
22-
2311
/**
2412
* Doubly-linked concurrent list node with remove support.
2513
* Based on paper
@@ -49,37 +37,10 @@ public actual open class LockFreeLinkedListNode {
4937
private fun removed(): Removed =
5038
_removedRef.value ?: Removed(this).also { _removedRef.lazySet(it) }
5139

52-
@PublishedApi
53-
internal abstract class CondAddOp(
54-
@JvmField val newNode: Node
55-
) : AtomicOp<Node>() {
56-
@JvmField var oldNext: Node? = null
57-
58-
override fun complete(affected: Node, failure: Any?) {
59-
val success = failure == null
60-
val update = if (success) newNode else oldNext
61-
if (update != null && affected._next.compareAndSet( this, update)) {
62-
// only the thread the makes this update actually finishes add operation
63-
if (success) newNode.finishAdd(oldNext!!)
64-
}
65-
}
66-
}
67-
68-
@PublishedApi
69-
internal inline fun makeCondAddOp(node: Node, crossinline condition: () -> Boolean): CondAddOp =
70-
object : CondAddOp(node) {
71-
override fun prepare(affected: Node): Any? = if (condition()) null else CONDITION_FALSE
72-
}
73-
7440
public actual open val isRemoved: Boolean get() = next is Removed
7541

7642
// LINEARIZABLE. Returns Node | Removed
77-
public val next: Any get() {
78-
_next.loop { next ->
79-
if (next !is OpDescriptor) return next
80-
next.perform(this)
81-
}
82-
}
43+
public val next: Any get() = _next.value
8344

8445
// LINEARIZABLE. Returns next non-removed Node
8546
public actual val nextNode: Node get() =
@@ -117,29 +78,30 @@ public actual open class LockFreeLinkedListNode {
11778
// ------ addLastXXX ------
11879

11980
/**
120-
* Adds last item to this list.
81+
* Adds last item to this list. Returns `false` if the list is closed.
12182
*/
122-
public actual fun addLast(node: Node) {
83+
public actual fun addLast(node: Node, allowedAfterPartialClosing: Boolean): Boolean {
12384
while (true) { // lock-free loop on prev.next
124-
if (prevNode.addNext(node, this)) return
85+
val currentPrev = prevNode
86+
return when {
87+
currentPrev is LIST_CLOSED_FOR_ALL -> false
88+
currentPrev is LIST_CLOSED_FOR_SOME ->
89+
allowedAfterPartialClosing && currentPrev.addLast(node, allowedAfterPartialClosing)
90+
currentPrev.addNext(node, this) -> true
91+
else -> continue
92+
}
12593
}
12694
}
12795

12896
/**
129-
* Adds last item to this list atomically if the [condition] is true.
97+
* Forbids adding some of the new items to this list.
13098
*/
131-
public actual inline fun addLastIf(node: Node, crossinline condition: () -> Boolean): Boolean {
132-
val condAdd = makeCondAddOp(node, condition)
133-
while (true) { // lock-free loop on prev.next
134-
val prev = prevNode // sentinel node is never removed, so prev is always defined
135-
when (prev.tryCondAddNext(node, this, condAdd)) {
136-
SUCCESS -> return true
137-
FAILURE -> return false
138-
}
139-
}
140-
}
99+
public actual fun closeForSome() { addLast(LIST_CLOSED_FOR_SOME(), allowedAfterPartialClosing = false) }
141100

142-
// ------ addXXX util ------
101+
/**
102+
* Forbids adding new items to this list.
103+
*/
104+
public actual fun close() { addLast(LIST_CLOSED_FOR_ALL(), allowedAfterPartialClosing = true) }
143105

144106
/**
145107
* Given:
@@ -174,17 +136,6 @@ public actual open class LockFreeLinkedListNode {
174136
return true
175137
}
176138

177-
// returns UNDECIDED, SUCCESS or FAILURE
178-
@PublishedApi
179-
internal fun tryCondAddNext(node: Node, next: Node, condAdd: CondAddOp): Int {
180-
node._prev.lazySet(this)
181-
node._next.lazySet(next)
182-
condAdd.oldNext = next
183-
if (!_next.compareAndSet(next, condAdd)) return UNDECIDED
184-
// added operation successfully (linearized) -- complete it & fixup the list
185-
return if (condAdd.perform(this) == null) SUCCESS else FAILURE
186-
}
187-
188139
// ------ removeXXX ------
189140

190141
/**
@@ -284,10 +235,6 @@ public actual open class LockFreeLinkedListNode {
284235
}
285236
// slow path when we need to help remove operations
286237
this.isRemoved -> return null // nothing to do, this node was removed, bail out asap to save time
287-
prevNext is OpDescriptor -> { // help & retry
288-
prevNext.perform(prev)
289-
return correctPrev() // retry from scratch
290-
}
291238
prevNext is Removed -> {
292239
if (last !== null) {
293240
// newly added (prev) node is already removed, correct last.next around it
@@ -347,3 +294,7 @@ public actual open class LockFreeLinkedListHead : LockFreeLinkedListNode() {
347294

348295
override fun nextIfRemoved(): Node? = null
349296
}
297+
298+
private class LIST_CLOSED_FOR_SOME: LockFreeLinkedListNode()
299+
300+
private class LIST_CLOSED_FOR_ALL: LockFreeLinkedListNode()

0 commit comments

Comments
 (0)