Skip to content

Commit 92f1c8e

Browse files
committed
~ Fixed node leaks during stress test, restore full validation
1 parent f19216c commit 92f1c8e

File tree

4 files changed

+28
-37
lines changed

4 files changed

+28
-37
lines changed

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

+17-25
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ public actual open class LockFreeLinkedListNode {
106106
// prev.next correction, which does not provide linearizable backwards iteration, but can be used to
107107
// resume forward iteration when current node was removed.
108108
public actual val prevNode: Node
109-
get() = correctPrev(null, true) ?: findPrevNonRemoved(_prev.value)
109+
get() = correctPrev(null) ?: findPrevNonRemoved(_prev.value)
110110

111111
private tailrec fun findPrevNonRemoved(current: Node): Node {
112112
if (!current.isRemoved) return current
@@ -249,7 +249,7 @@ public actual open class LockFreeLinkedListNode {
249249
val removed = (next as Node).removed()
250250
if (_next.compareAndSet(next, removed)) {
251251
// was removed successfully (linearized remove) -- fixup the list
252-
next.correctPrev(null, false)
252+
next.correctPrev(null)
253253
return null
254254
}
255255
}
@@ -272,7 +272,7 @@ public actual open class LockFreeLinkedListNode {
272272
node = next.ref
273273
}
274274
// Found a non-removed node
275-
node.correctPrev(null, false)
275+
node.correctPrev(null)
276276
}
277277

278278
public actual fun removeFirstOrNull(): Node? {
@@ -332,7 +332,7 @@ public actual open class LockFreeLinkedListNode {
332332

333333
// Returns null when atomic op got into deadlock trying to help operation that started later (RETRY_ATOMIC)
334334
final override fun takeAffectedNode(op: OpDescriptor): Node? =
335-
queue.correctPrev(op, true) // queue head is never removed, so null result can only mean RETRY_ATOMIC
335+
queue.correctPrev(op) // queue head is never removed, so null result can only mean RETRY_ATOMIC
336336

337337
private val _affectedNode = atomic<Node?>(null)
338338
final override val affectedNode: Node? get() = _affectedNode.value
@@ -407,7 +407,7 @@ public actual open class LockFreeLinkedListNode {
407407
final override fun finishOnSuccess(affected: Node, next: Node) {
408408
// Complete removal operation here. It bails out if next node is also removed and it becomes
409409
// responsibility of the next's removes to call correctPrev which would help fix all the links.
410-
next.correctPrev(null, false)
410+
next.correctPrev(null)
411411
}
412412
}
413413

@@ -432,7 +432,7 @@ public actual open class LockFreeLinkedListNode {
432432
if (affected._next.compareAndSet(this, removed)) {
433433
// Complete removal operation here. It bails out if next node is also removed and it becomes
434434
// responsibility of the next's removes to call correctPrev which would help fix all the links.
435-
next.correctPrev(null, false)
435+
next.correctPrev(null)
436436
}
437437
return REMOVE_PREPARED
438438
}
@@ -564,12 +564,8 @@ public actual open class LockFreeLinkedListNode {
564564
* * When [op] descriptor is not `null` and and operation descriptor that is [OpDescriptor.isEarlierThan]
565565
* that current [op] is found while traversing the list. This `null` result will be translated
566566
* by callers to [RETRY_ATOMIC].
567-
*
568-
* @param [fixupPrev] when set to `true` this function ensure that `this.prev.next === this` after return from
569-
* this call. Otherwise, it will not retry on CAS failure. It is set to `true` when
570-
* looking for a previous node in [prevNode] in a linearizable way.
571567
*/
572-
private tailrec fun correctPrev(op: OpDescriptor?, fixupPrev: Boolean): Node? {
568+
private tailrec fun correctPrev(op: OpDescriptor?): Node? {
573569
val oldPrev = _prev.value
574570
var prev: Node = oldPrev
575571
var last: Node? = null // will be set so that last.next === prev
@@ -580,9 +576,9 @@ public actual open class LockFreeLinkedListNode {
580576
prevNext === this -> {
581577
if (oldPrev === prev) return prev // nothing to update -- all is fine, prev found
582578
// otherwise need to update prev
583-
if (!this._prev.compareAndSet(oldPrev, prev) && fixupPrev) {
584-
// Note: retry from scratch on failure to update prev only when fixupPrev is true
585-
return correctPrev(op, true)
579+
if (!this._prev.compareAndSet(oldPrev, prev)) {
580+
// Note: retry from scratch on failure to update prev
581+
return correctPrev(op)
586582
}
587583
return prev // return a correct prev
588584
}
@@ -593,13 +589,13 @@ public actual open class LockFreeLinkedListNode {
593589
if (op != null && op.isEarlierThan(prevNext))
594590
return null // RETRY_ATOMIC
595591
prevNext.perform(prev)
596-
return correctPrev(op, fixupPrev) // retry from scratch
592+
return correctPrev(op) // retry from scratch
597593
}
598594
prevNext is Removed -> {
599595
if (last !== null) {
600596
// newly added (prev) node is already removed, correct last.next around it
601597
if (!last._next.compareAndSet(prev, prevNext.ref)) {
602-
return correctPrev(op, fixupPrev) // retry from scratch on failure to update next
598+
return correctPrev(op) // retry from scratch on failure to update next
603599
}
604600
prev = last
605601
last = null
@@ -615,12 +611,8 @@ public actual open class LockFreeLinkedListNode {
615611
}
616612
}
617613

618-
internal fun validateNode(prev: Node, next: Node, stress: Boolean) {
619-
if (!stress) {
620-
// during stress test prev can be corrupted and it is Ok. It does not
621-
// have to absolutely correct as it is fixed on "as needed" basis
622-
assert { prev === this._prev.value }
623-
}
614+
internal fun validateNode(prev: Node, next: Node) {
615+
assert { prev === this._prev.value }
624616
assert { next === this._next.value }
625617
}
626618

@@ -660,15 +652,15 @@ public actual open class LockFreeLinkedListHead : LockFreeLinkedListNode() {
660652
override val isRemoved: Boolean get() = false
661653
override fun nextIfRemoved(): Node? = null
662654

663-
internal fun validate(stress: Boolean = false) {
655+
internal fun validate() {
664656
var prev: Node = this
665657
var cur: Node = next as Node
666658
while (cur != this) {
667659
val next = cur.nextNode
668-
cur.validateNode(prev, next, stress)
660+
cur.validateNode(prev, next)
669661
prev = cur
670662
cur = next
671663
}
672-
validateNode(prev, next as Node, stress)
664+
validateNode(prev, next as Node)
673665
}
674666
}

kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListAtomicLFStressTest.kt

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

55
package kotlinx.coroutines.internal
66

7-
import kotlinx.atomicfu.LockFreedomTestEnvironment
8-
import kotlinx.coroutines.stressTestMultiplier
7+
import kotlinx.atomicfu.*
8+
import kotlinx.coroutines.*
9+
import org.junit.*
910
import org.junit.Assert.*
10-
import org.junit.Test
1111
import java.util.*
12+
import java.util.concurrent.atomic.*
1213
import java.util.concurrent.atomic.AtomicLong
13-
import java.util.concurrent.atomic.AtomicReference
1414

1515
/**
1616
* This stress test has 4 threads adding randomly to the list and them immediately undoing
@@ -103,7 +103,7 @@ class LockFreeLinkedListAtomicLFStressTest {
103103
assertEquals(missed.get(), removed.get())
104104
assertTrue(undone.get() > 0)
105105
assertTrue(missed.get() > 0)
106-
lists.forEach { it.validate(stress = true) }
106+
lists.forEach { it.validate() }
107107
}
108108

109109
private fun addLastOp(list: LockFreeLinkedListHead, node: IntNode) {

kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListLongStressTest.kt

+5-6
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@
44

55
package kotlinx.coroutines.internal
66

7-
import kotlinx.coroutines.TestBase
8-
import org.junit.Test
7+
import kotlinx.coroutines.*
8+
import org.junit.*
99
import java.util.*
10-
import java.util.concurrent.atomic.AtomicInteger
11-
import kotlin.concurrent.thread
12-
import kotlin.sequences.buildIterator
10+
import java.util.concurrent.atomic.*
11+
import kotlin.concurrent.*
1312

1413
/**
1514
* This stress test has 2 threads adding on one side on list, 2 more threads adding on the other,
@@ -60,7 +59,7 @@ class LockFreeLinkedListLongStressTest : TestBase() {
6059
thread.join()
6160
// verification
6261
println("Verify result")
63-
list.validate(stress = true)
62+
list.validate()
6463
val expected = iterator {
6564
for (i in 0 until nAdded)
6665
if (!shallRemove(i))

kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListShortStressTest.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,6 @@ class LockFreeLinkedListShortStressTest : TestBase() {
8383
assertEquals(missed.get(), removed.get())
8484
assertTrue(undone.get() > 0)
8585
assertTrue(missed.get() > 0)
86-
list.validate(stress = true)
86+
list.validate()
8787
}
8888
}

0 commit comments

Comments
 (0)