Skip to content

Commit 299eecd

Browse files
committed
~ Cleaner list dispose
1 parent a331182 commit 299eecd

File tree

2 files changed

+31
-38
lines changed

2 files changed

+31
-38
lines changed

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

+28-27
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,9 @@ public actual typealias PrepareOp = LockFreeLinkedListNode.PrepareOp
5757
@Suppress("LeakingThis")
5858
@InternalCoroutinesApi
5959
public actual open class LockFreeLinkedListNode {
60-
private val _next = atomic<Any>(this) // Node | Removed | OpDescriptor
61-
private val _prev = atomic<Any>(this) // Node | Removed
60+
// those _next & _prev refs can be null on Kotlin/Native when doubly-linked list is disposed
61+
private val _next = atomic<Any?>(this) // Node | Removed | OpDescriptor
62+
private val _prev = atomic<Any?>(this) // Node | Removed
6263
private val _removedRef = atomic<Removed?>(null) // lazily cached removed ref to this
6364

6465
private fun removed(): Removed =
@@ -93,28 +94,30 @@ public actual open class LockFreeLinkedListNode {
9394
public actual val isRemoved: Boolean get() = next is Removed
9495

9596
// LINEARIZABLE. Returns Node | Removed
96-
public val next: Any get() {
97+
// Returns null for the disposed node on Kotlin/Native
98+
public val next: Any? get() {
9799
_next.loop { next ->
98100
if (next !is OpDescriptor) return next
99101
next.perform(this)
100102
}
101103
}
102104

103105
// LINEARIZABLE. Returns next non-removed Node
104-
public actual val nextNode: Node get() = next.unwrap()
106+
public actual val nextNode: Node get() = next!!.unwrap()
105107

106108
// LINEARIZABLE. Returns Node | Removed
107-
public val prev: Any get() {
109+
// Returns null for the disposed node on Kotlin/Native
110+
public val prev: Any? get() {
108111
_prev.loop { prev ->
109-
if (prev is Removed) return prev
112+
if (prev is Removed?) return prev
110113
prev as Node // otherwise, it can be only node
111114
if (prev.next === this) return prev
112115
correctPrev(prev, null)
113116
}
114117
}
115118

116119
// LINEARIZABLE. Returns prev non-removed Node
117-
public actual val prevNode: Node get() = prev.unwrap()
120+
public actual val prevNode: Node get() = prev!!.unwrap()
118121

119122
// ------ addOneIfEmpty ------
120123

@@ -152,7 +155,8 @@ public actual open class LockFreeLinkedListNode {
152155
public actual inline fun addLastIf(node: Node, crossinline condition: () -> Boolean): Boolean {
153156
val condAdd = makeCondAddOp(node, condition)
154157
while (true) { // lock-free loop on prev.next
155-
val prev = prev as Node // sentinel node is never removed, so prev is always defined
158+
// sentinel node is never removed, so prev is always defined, but can be concurrently disposed on Kotlin/Native
159+
val prev = prev as Node? ?: return false
156160
when (prev.tryCondAddNext(node, this, condAdd)) {
157161
SUCCESS -> return true
158162
FAILURE -> return false
@@ -243,7 +247,7 @@ public actual open class LockFreeLinkedListNode {
243247
public actual open fun remove(): Boolean {
244248
while (true) { // lock-free loop on next
245249
val next = this.next
246-
if (next is Removed) return false // was already removed -- don't try to help (original thread will take care)
250+
if (next is Removed?) return false // was already removed -- don't try to help (original thread will take care)
247251
if (next === this) return false // was not even added
248252
val removed = (next as Node).removed()
249253
if (_next.compareAndSet(next, removed)) {
@@ -469,6 +473,7 @@ public actual open class LockFreeLinkedListNode {
469473
continue // and retry
470474
}
471475
// next: Node | Removed
476+
next as Any
472477
val failure = failure(affected)
473478
if (failure != null) return failure // signal failure
474479
if (retry(affected, next)) continue // retry operation
@@ -539,7 +544,8 @@ public actual open class LockFreeLinkedListNode {
539544

540545
private fun finishRemove(next: Node) {
541546
helpDelete()
542-
next.correctPrev(_prev.value.unwrap(), null)
547+
val prev = _prev.value ?: return
548+
next.correctPrev(prev.unwrap(), null)
543549
}
544550

545551
private fun markPrev(): Node {
@@ -592,22 +598,22 @@ public actual open class LockFreeLinkedListNode {
592598
var next: Node = (this._next.value as Removed).ref
593599
while (true) {
594600
// move to the right until first non-removed node
595-
val nextNext = next.next
601+
val nextNext = next.next ?: return
596602
if (nextNext is Removed) {
597603
next.markPrev()
598604
next = nextNext.ref
599605
continue
600606
}
601607
// move the the left until first non-removed node
602-
val prevNext = prev.next
608+
val prevNext = prev.next ?: return
603609
if (prevNext is Removed) {
604610
if (last != null) {
605611
prev.markPrev()
606612
last._next.compareAndSet(prev, prevNext.ref)
607613
prev = last
608614
last = null
609615
} else {
610-
prev = prev._prev.value.unwrap()
616+
prev = (prev._prev.value ?: return).unwrap()
611617
}
612618
continue
613619
}
@@ -644,11 +650,11 @@ public actual open class LockFreeLinkedListNode {
644650
prev = last
645651
last = null
646652
} else {
647-
prev = prev._prev.value.unwrap()
653+
prev = (prev._prev.value ?: return null).unwrap()
648654
}
649655
continue
650656
}
651-
val oldPrev = this._prev.value
657+
val oldPrev = this._prev.value ?: return null
652658
if (oldPrev is Removed) return null // this node was removed, too -- its remover will take care
653659
if (prevNext !== this) {
654660
// need to fixup next
@@ -671,17 +677,12 @@ public actual open class LockFreeLinkedListNode {
671677
/**
672678
* Only needed on Kotlin/Native. See [disposeLockFreeLinkedList].
673679
*/
674-
internal fun unlinkRefs(nullRef: Node) {
675-
_next.value = nullRef
676-
_prev.value = nullRef
677-
_removedRef.value = nullRef.removed()
680+
internal fun unlinkRefs(last: Boolean) {
681+
if (last) _next.value = null
682+
_prev.value = null
683+
_removedRef.value = null
678684
}
679685

680-
/**
681-
* Only needed on Kotlin/Native. See [disposeLockFreeLinkedList].
682-
*/
683-
internal fun initRemoved() { removed() }
684-
685686
override fun toString(): String = "$classSimpleName@$hexAddress"
686687
}
687688

@@ -704,10 +705,10 @@ public actual open class LockFreeLinkedListHead : LockFreeLinkedListNode() {
704705
* Iterates over all elements in this list of a specified type.
705706
*/
706707
public actual inline fun <reified T : Node> forEach(block: (T) -> Unit) {
707-
var cur: Node = next as Node
708-
while (cur != this) {
708+
var cur: Node? = next as Node?
709+
while (cur != this && cur != null) {
709710
if (cur is T) block(cur)
710-
cur = cur.nextNode
711+
cur = cur.next?.unwrap()
711712
}
712713
}
713714

kotlinx-coroutines-core/native/src/internal/ManualMemoryManagement.kt

+3-11
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,15 @@
44

55
package kotlinx.coroutines.internal
66

7-
import kotlin.native.concurrent.*
8-
97
@Suppress("NOTHING_TO_INLINE")
108
internal actual inline fun disposeLockFreeLinkedList(list: () -> LockFreeLinkedListNode?) {
119
// only needed on Kotlin/Native
1210
val head = list() ?: return
1311
var cur = head
1412
do {
1513
val next = cur.nextNode
16-
cur.unlinkRefs(NullNodeRef)
14+
val last = next === head
15+
cur.unlinkRefs(last)
1716
cur = next
18-
} while (cur !== head)
19-
}
20-
21-
private object NullNodeRef : LockFreeLinkedListNode() {
22-
init {
23-
initRemoved()
24-
freeze()
25-
}
17+
} while (!last)
2618
}

0 commit comments

Comments
 (0)