Skip to content

Commit ea913b0

Browse files
committed
Remove DCSS
1 parent 7353c11 commit ea913b0

File tree

8 files changed

+96
-173
lines changed

8 files changed

+96
-173
lines changed

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

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

169164
/**
170165
* @suppress **This is unstable API and it is subject to change.**
@@ -324,6 +319,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
324319
private fun notifyCancelling(list: NodeList, cause: Throwable) {
325320
// first cancel our own children
326321
onCancelling(cause)
322+
list.closeForSome()
327323
notifyHandlers(list, cause) { it.onCancelling }
328324
// then cancel parent
329325
cancelParent(cause) // tentative cancellation -- does not matter if there is no parent
@@ -355,8 +351,10 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
355351
return parent.childCancelled(cause) || isCancellation
356352
}
357353

358-
private fun NodeList.notifyCompletion(cause: Throwable?) =
354+
private fun NodeList.notifyCompletion(cause: Throwable?) {
355+
close()
359356
notifyHandlers(this, cause) { true }
357+
}
360358

361359
private inline fun notifyHandlers(list: NodeList, cause: Throwable?, predicate: (JobNode) -> Boolean) {
362360
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 || node 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 = node 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) node.invoke(rootCause)
502504
return handle
503-
} else {
504-
if (addLastAtomic(state, list, node)) return node
505+
} else if (list.addLast(
506+
node, allowedAfterPartialClosing = !node.onCancelling || node is ChildHandleNode
507+
)) {
508+
if (node 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+
assert { invokeImmediately }
519+
synchronized(latestState) { latestState.rootCause }?.let { node.invoke(it) }
520+
}
521+
}
522+
return node
505523
}
506524
}
507525
}
@@ -515,9 +533,6 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
515533
}
516534
}
517535

518-
private fun addLastAtomic(expect: Any, list: NodeList, node: JobNode) =
519-
list.addLastIf(node) { this.state === expect }
520-
521536
private fun promoteEmptyToNodeList(state: Empty) {
522537
// try to promote it to LIST state with the corresponding state
523538
val list = NodeList()

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

Lines changed: 0 additions & 67 deletions
This file was deleted.

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

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,18 @@
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
10+
public fun addLast(node: LockFreeLinkedListNode, allowedAfterPartialClosing: Boolean): Boolean
1311
public fun addOneIfEmpty(node: LockFreeLinkedListNode): Boolean
14-
public inline fun addLastIf(node: LockFreeLinkedListNode, crossinline condition: () -> Boolean): Boolean
1512
public open fun remove(): Boolean
16-
13+
public fun close()
14+
public fun closeForSome()
1715
}
1816

19-
internal fun LockFreeLinkedListNode.addLast(node: LockFreeLinkedListNode) = addLastIf(node) { true }
20-
2117
/** @suppress **This is unstable API and it is subject to change.** */
2218
public expect open class LockFreeLinkedListHead() : LockFreeLinkedListNode {
2319
public inline fun forEach(block: (LockFreeLinkedListNode) -> Unit)

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

Lines changed: 14 additions & 0 deletions
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

Lines changed: 23 additions & 63 deletions
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,20 +78,30 @@ public actual open class LockFreeLinkedListNode {
11778
// ------ addLastXXX ------
11879

11980
/**
120-
* Adds last item to this list atomically if the [condition] is true.
81+
* Adds last item to this list. Returns `false` if the list is closed.
12182
*/
122-
public actual inline fun addLastIf(node: Node, crossinline condition: () -> Boolean): Boolean {
123-
val condAdd = makeCondAddOp(node, condition)
83+
public actual fun addLast(node: Node, allowedAfterPartialClosing: Boolean): Boolean {
12484
while (true) { // lock-free loop on prev.next
125-
val prev = prevNode // sentinel node is never removed, so prev is always defined
126-
when (prev.tryCondAddNext(node, this, condAdd)) {
127-
SUCCESS -> return true
128-
FAILURE -> return false
85+
val currentPrev = prevNode
86+
return when {
87+
currentPrev is ListClosedForAll -> false
88+
currentPrev is ListClosedForSome ->
89+
allowedAfterPartialClosing && currentPrev.addLast(node, allowedAfterPartialClosing)
90+
currentPrev.addNext(node, this) -> true
91+
else -> continue
12992
}
13093
}
13194
}
13295

133-
// ------ addXXX util ------
96+
/**
97+
* Forbids adding some of the new items to this list.
98+
*/
99+
public actual fun closeForSome() { addLast(ListClosedForSome(), allowedAfterPartialClosing = false) }
100+
101+
/**
102+
* Forbids adding new items to this list.
103+
*/
104+
public actual fun close() { addLast(ListClosedForAll(), allowedAfterPartialClosing = true) }
134105

135106
/**
136107
* Given:
@@ -165,17 +136,6 @@ public actual open class LockFreeLinkedListNode {
165136
return true
166137
}
167138

168-
// returns UNDECIDED, SUCCESS or FAILURE
169-
@PublishedApi
170-
internal fun tryCondAddNext(node: Node, next: Node, condAdd: CondAddOp): Int {
171-
node._prev.lazySet(this)
172-
node._next.lazySet(next)
173-
condAdd.oldNext = next
174-
if (!_next.compareAndSet(next, condAdd)) return UNDECIDED
175-
// added operation successfully (linearized) -- complete it & fixup the list
176-
return if (condAdd.perform(this) == null) SUCCESS else FAILURE
177-
}
178-
179139
// ------ removeXXX ------
180140

181141
/**
@@ -273,10 +233,6 @@ public actual open class LockFreeLinkedListNode {
273233
}
274234
// slow path when we need to help remove operations
275235
this.isRemoved -> return null // nothing to do, this node was removed, bail out asap to save time
276-
prevNext is OpDescriptor -> { // help & retry
277-
prevNext.perform(prev)
278-
return correctPrev() // retry from scratch
279-
}
280236
prevNext is Removed -> {
281237
if (last !== null) {
282238
// newly added (prev) node is already removed, correct last.next around it
@@ -332,3 +288,7 @@ public actual open class LockFreeLinkedListHead : LockFreeLinkedListNode() {
332288
// optimization: because head is never removed, we don't have to read _next.value to check these:
333289
override val isRemoved: Boolean get() = false
334290
}
291+
292+
private class ListClosedForSome: LockFreeLinkedListNode()
293+
294+
private class ListClosedForAll: LockFreeLinkedListNode()

kotlinx-coroutines-core/jsAndWasmShared/src/internal/LinkedList.kt

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,24 @@ public actual open class LockFreeLinkedListNode {
1414
inline actual val prevNode get() = _prev
1515
inline actual val isRemoved get() = _removed
1616

17-
@PublishedApi
18-
internal fun addLast(node: Node) {
19-
val prev = this._prev
20-
node._next = this
21-
node._prev = prev
22-
prev._next = node
23-
this._prev = node
17+
public actual fun addLast(node: Node, allowedAfterPartialClosing: Boolean): Boolean = when (val prev = this._prev) {
18+
is ListClosedForAll -> false
19+
is ListClosedForSome -> allowedAfterPartialClosing && prev.addLast(node, allowedAfterPartialClosing)
20+
else -> {
21+
node._next = this
22+
node._prev = prev
23+
prev._next = node
24+
this._prev = node
25+
true
26+
}
27+
}
28+
29+
public actual fun closeForSome() {
30+
addLast(ListClosedForSome(), allowedAfterPartialClosing = true)
31+
}
32+
33+
public actual fun close() {
34+
addLast(ListClosedForAll(), allowedAfterPartialClosing = false)
2435
}
2536

2637
/*
@@ -30,10 +41,6 @@ public actual open class LockFreeLinkedListNode {
3041
* invokes handler on remove
3142
*/
3243
public actual open fun remove(): Boolean {
33-
return removeImpl()
34-
}
35-
36-
private fun removeImpl(): Boolean {
3744
if (_removed) return false
3845
val prev = this._prev
3946
val next = this._next
@@ -45,13 +52,7 @@ public actual open class LockFreeLinkedListNode {
4552

4653
public actual fun addOneIfEmpty(node: Node): Boolean {
4754
if (_next !== this) return false
48-
addLast(node)
49-
return true
50-
}
51-
52-
public actual inline fun addLastIf(node: Node, crossinline condition: () -> Boolean): Boolean {
53-
if (!condition()) return false
54-
addLast(node)
55+
addLast(node, allowedAfterPartialClosing = false)
5556
return true
5657
}
5758
}
@@ -72,3 +73,7 @@ public actual open class LockFreeLinkedListHead : Node() {
7273
// just a defensive programming -- makes sure that list head sentinel is never removed
7374
public actual final override fun remove(): Nothing = throw UnsupportedOperationException()
7475
}
76+
77+
private class ListClosedForSome: LockFreeLinkedListNode()
78+
79+
private class ListClosedForAll: LockFreeLinkedListNode()

0 commit comments

Comments
 (0)