Skip to content

Commit 94b644f

Browse files
committed
Simpler and faster lock-free linked list
Lock-free list implementation is considerably simplified, taking into account a limited number of operations that it needs to support. * prev pointers in the list are not marked for removal, since we don't need to support linearizable backwards iteration. * helpDelete method is completely removed. All "delete-helping" is performed only by correctPrev method. * correctPrev method bails out when the node it works on is removed to reduce contention during concurrent removals. * Special open methods "isRemoved" and "nextIfRemoved" are introduced and are overridden in list head class (which is never removed). This ensures that on long list "removeFist" operation (touching head) does not interfere with "addLast" (touch tail). There is still sharing of cache-lines in this case, but no helping between them. All in all, this improvement reduces the size of implementation code and makes it considerably faster. Operations on LinkedListChannel are now much faster (see timings of ChannelSendReceiveStressTest). Additionally, change also reduces amount of wasteful helping and starvation of ConflatedChannel receiver with many senders: * removeFirstIfIsInstanceOfOrPeekIf that is used to take the first element from a channel does not need help every time when it finds a removed element, but quickly tries the next element to see if it can remove that one instead for a few times. * conflatePreviousSendBuffered does not help with removal when its own node was removed.
1 parent bbf198b commit 94b644f

File tree

6 files changed

+176
-200
lines changed

6 files changed

+176
-200
lines changed

Diff for: kotlinx-coroutines-core/common/src/channels/ConflatedChannel.kt

+5-4
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
package kotlinx.coroutines.channels
66

7-
import kotlinx.coroutines.selects.*
87
import kotlinx.coroutines.internal.*
8+
import kotlinx.coroutines.selects.*
99

1010
/**
1111
* Channel that buffers at most one element and conflates all subsequent `send` and `offer` invocations,
@@ -46,13 +46,14 @@ internal open class ConflatedChannel<E> : AbstractChannel<E>() {
4646
}
4747

4848
private fun conflatePreviousSendBuffered(node: SendBuffered<E>) {
49-
// Conflate all previous SendBuffered, helping other sends to conflate
50-
var prev = node.prevNode
49+
// Conflate all previous SendBuffered, helping other sends to conflate, but only as long
50+
// as this node itself is not removed
51+
var prev = node.prevNodeIfNotRemoved ?: return
5152
while (prev is SendBuffered<*>) {
5253
if (!prev.remove()) {
5354
prev.helpRemove()
5455
}
55-
prev = prev.prevNode
56+
prev = prev.prevNodeIfNotRemoved ?: return
5657
}
5758
}
5859

Diff for: kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public expect open class LockFreeLinkedListNode() {
1111
public val isRemoved: Boolean
1212
public val nextNode: LockFreeLinkedListNode
1313
public val prevNode: LockFreeLinkedListNode
14+
public val prevNodeIfNotRemoved: LockFreeLinkedListNode?
1415
public fun addLast(node: LockFreeLinkedListNode)
1516
public fun addOneIfEmpty(node: LockFreeLinkedListNode): Boolean
1617
public inline fun addLastIf(node: LockFreeLinkedListNode, crossinline condition: () -> Boolean): Boolean

Diff for: kotlinx-coroutines-core/js/src/internal/LinkedList.kt

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public open class LinkedListNode {
2323

2424
public inline val nextNode get() = _next
2525
public inline val prevNode get() = _prev
26+
public inline val prevNodeIfNotRemoved: LockFreeLinkedListNode? get() = _prev.takeIf { !_removed }
2627
public inline val isRemoved get() = _removed
2728

2829
public fun addLast(node: Node) {

0 commit comments

Comments
 (0)