Skip to content

Commit 8a76e31

Browse files
committed
Generalize the concept of list closing to any number of levels
1 parent a69748c commit 8a76e31

File tree

6 files changed

+31
-41
lines changed

6 files changed

+31
-41
lines changed

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
319319
private fun notifyCancelling(list: NodeList, cause: Throwable) {
320320
// first cancel our own children
321321
onCancelling(cause)
322-
list.closeForSome()
322+
list.close(LIST_CANCELLATION_PERMISSION)
323323
notifyHandlers(list, cause) { it.onCancelling }
324324
// then cancel parent
325325
cancelParent(cause) // tentative cancellation -- does not matter if there is no parent
@@ -352,7 +352,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
352352
}
353353

354354
private fun NodeList.notifyCompletion(cause: Throwable?) {
355-
close()
355+
close(LIST_MAX_PERMISSION)
356356
notifyHandlers(this, cause) { true }
357357
}
358358

@@ -468,13 +468,13 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
468468
if (node.onCancelling) {
469469
val rootCause = (state as? Finishing)?.let { synchronized(it) { it.rootCause } }
470470
if (rootCause == null) {
471-
list.addLast(node, allowedAfterPartialClosing = false)
471+
list.addLast(node, LIST_CANCELLATION_PERMISSION)
472472
} else {
473473
if (invokeImmediately) node.invoke(rootCause)
474474
return NonDisposableHandle
475475
}
476476
} else {
477-
list.addLast(node, allowedAfterPartialClosing = true)
477+
list.addLast(node, LIST_MAX_PERMISSION)
478478
}
479479
}
480480
when {
@@ -977,7 +977,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
977977
// or we are adding a child to a coroutine that is not completing yet
978978
if (maybeRootCause == null || !state.isCompleting) {
979979
// Note: add node the list while holding lock on state (make sure it cannot change)
980-
if (!list.addLast(node, allowedAfterPartialClosing = true))
980+
if (!list.addLast(node, LIST_MAX_PERMISSION))
981981
return@tryPutNodeIntoList false // retry
982982
// just return the node if we don't have to invoke the handler (not cancelling yet)
983983
rootCause = maybeRootCause ?: return@tryPutNodeIntoList true
@@ -990,7 +990,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
990990
}
991991
node.invoke(rootCause)
992992
return handle
993-
} else list.addLast(node, allowedAfterPartialClosing = true).also { success ->
993+
} else list.addLast(node, LIST_MAX_PERMISSION).also { success ->
994994
if (success) {
995995
/** Handling the following case:
996996
* - A child requested to be added to the list;
@@ -1339,6 +1339,9 @@ private val SEALED = Symbol("SEALED")
13391339
private val EMPTY_NEW = Empty(false)
13401340
private val EMPTY_ACTIVE = Empty(true)
13411341

1342+
private const val LIST_MAX_PERMISSION = Int.MAX_VALUE
1343+
private const val LIST_CANCELLATION_PERMISSION = 0
1344+
13421345
private class Empty(override val isActive: Boolean) : Incomplete {
13431346
override val list: NodeList? get() = null
13441347
override fun toString(): String = "Empty{${if (isActive) "Active" else "New" }}"

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ public expect open class LockFreeLinkedListNode() {
77
public val isRemoved: Boolean
88
public val nextNode: LockFreeLinkedListNode
99
public val prevNode: LockFreeLinkedListNode
10-
public fun addLast(node: LockFreeLinkedListNode, allowedAfterPartialClosing: Boolean): Boolean
10+
public fun addLast(node: LockFreeLinkedListNode, clearanceLevel: Int): Boolean
1111
public fun addOneIfEmpty(node: LockFreeLinkedListNode): Boolean
1212
public open fun remove(): Boolean
13-
public fun close()
14-
public fun closeForSome()
13+
14+
/**
15+
* Closes the list for [maxForbidden] and all numbers below.
16+
*/
17+
public fun close(maxForbidden: Int)
1518
}
1619

1720
/** @suppress **This is unstable API and it is subject to change.** */

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

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -80,28 +80,22 @@ public actual open class LockFreeLinkedListNode {
8080
/**
8181
* Adds last item to this list. Returns `false` if the list is closed.
8282
*/
83-
public actual fun addLast(node: Node, allowedAfterPartialClosing: Boolean): Boolean {
83+
public actual fun addLast(node: Node, clearanceLevel: Int): Boolean {
8484
while (true) { // lock-free loop on prev.next
8585
val currentPrev = prevNode
8686
return when {
87-
currentPrev is ListClosedForAll -> false
88-
currentPrev is ListClosedForSome ->
89-
allowedAfterPartialClosing && currentPrev.addLast(node, allowedAfterPartialClosing)
87+
currentPrev is ListClosed ->
88+
currentPrev.maxForbidden < clearanceLevel && currentPrev.addLast(node, clearanceLevel)
9089
currentPrev.addNext(node, this) -> true
9190
else -> continue
9291
}
9392
}
9493
}
9594

96-
/**
97-
* Forbids adding some of the new items to this list.
98-
*/
99-
public actual fun closeForSome() { addLast(ListClosedForSome(), allowedAfterPartialClosing = false) }
100-
10195
/**
10296
* Forbids adding new items to this list.
10397
*/
104-
public actual fun close() { addLast(ListClosedForAll(), allowedAfterPartialClosing = true) }
98+
public actual fun close(maxForbidden: Int) { addLast(ListClosed(maxForbidden), maxForbidden) }
10599

106100
/**
107101
* Given:
@@ -289,6 +283,4 @@ public actual open class LockFreeLinkedListHead : LockFreeLinkedListNode() {
289283
override val isRemoved: Boolean get() = false
290284
}
291285

292-
private class ListClosedForSome: LockFreeLinkedListNode()
293-
294-
private class ListClosedForAll: LockFreeLinkedListNode()
286+
private class ListClosed(val maxForbidden: Int): LockFreeLinkedListNode()

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

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

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)
17+
public actual fun addLast(node: Node, clearanceLevel: Int): Boolean = when (val prev = this._prev) {
18+
is ListClosed -> prev.maxForbidden < clearanceLevel && prev.addLast(node, clearanceLevel)
2019
else -> {
2120
node._next = this
2221
node._prev = prev
@@ -26,12 +25,8 @@ public actual open class LockFreeLinkedListNode {
2625
}
2726
}
2827

29-
public actual fun closeForSome() {
30-
addLast(ListClosedForSome(), allowedAfterPartialClosing = true)
31-
}
32-
33-
public actual fun close() {
34-
addLast(ListClosedForAll(), allowedAfterPartialClosing = false)
28+
public actual fun close(maxForbidden: Int) {
29+
addLast(ListClosed(maxForbidden), maxForbidden)
3530
}
3631

3732
/*
@@ -52,7 +47,7 @@ public actual open class LockFreeLinkedListNode {
5247

5348
public actual fun addOneIfEmpty(node: Node): Boolean {
5449
if (_next !== this) return false
55-
addLast(node, allowedAfterPartialClosing = false)
50+
addLast(node, Int.MIN_VALUE)
5651
return true
5752
}
5853
}
@@ -74,6 +69,4 @@ public actual open class LockFreeLinkedListHead : Node() {
7469
public actual final override fun remove(): Nothing = throw UnsupportedOperationException()
7570
}
7671

77-
private class ListClosedForSome: LockFreeLinkedListNode()
78-
79-
private class ListClosedForAll: LockFreeLinkedListNode()
72+
private class ListClosed(val maxForbidden: Int): LockFreeLinkedListNode()

kotlinx-coroutines-core/jsAndWasmShared/test/internal/LinkedListTest.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ class LinkedListTest {
1212
fun testSimpleAddLastRemove() {
1313
val list = LockFreeLinkedListHead()
1414
assertContents(list)
15-
val n1 = IntNode(1).apply { list.addLast(this, allowedAfterPartialClosing = false) }
15+
val n1 = IntNode(1).apply { list.addLast(this, Int.MAX_VALUE) }
1616
assertContents(list, 1)
17-
val n2 = IntNode(2).apply { list.addLast(this, allowedAfterPartialClosing = false) }
17+
val n2 = IntNode(2).apply { list.addLast(this, Int.MAX_VALUE) }
1818
assertContents(list, 1, 2)
19-
val n3 = IntNode(3).apply { list.addLast(this, allowedAfterPartialClosing = false) }
19+
val n3 = IntNode(3).apply { list.addLast(this, Int.MAX_VALUE) }
2020
assertContents(list, 1, 2, 3)
21-
val n4 = IntNode(4).apply { list.addLast(this, allowedAfterPartialClosing = false) }
21+
val n4 = IntNode(4).apply { list.addLast(this, Int.MAX_VALUE) }
2222
assertContents(list, 1, 2, 3, 4)
2323
assertTrue(n1.remove())
2424
assertContents(list, 2, 3, 4)

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package kotlinx.coroutines.internal
22

3-
import kotlinx.coroutines.testing.*
43
import kotlinx.coroutines.testing.TestBase
54
import org.junit.Test
65
import java.util.*
@@ -31,7 +30,7 @@ class LockFreeLinkedListLongStressTest : TestBase() {
3130
for (j in 0 until nAddThreads)
3231
threads += thread(start = false, name = "adder-$j") {
3332
for (i in j until nAdded step nAddThreads) {
34-
list.addLast(IntNode(i), allowedAfterPartialClosing = false)
33+
list.addLast(IntNode(i), Int.MAX_VALUE)
3534
}
3635
println("${Thread.currentThread().name} completed")
3736
workingAdders.decrementAndGet()

0 commit comments

Comments
 (0)