Commit a1d3cf0f authored by Him188's avatar Him188

LockFreeLinkedList

parent 4b28f004
...@@ -6,67 +6,104 @@ import kotlinx.atomicfu.AtomicRef ...@@ -6,67 +6,104 @@ import kotlinx.atomicfu.AtomicRef
import kotlinx.atomicfu.atomic import kotlinx.atomicfu.atomic
import net.mamoe.mirai.utils.Node.Companion.equals import net.mamoe.mirai.utils.Node.Companion.equals
@MiraiExperimentalAPI
inline fun <E> lockFreeLinkedListOf(vararg elements: E): LockFreeLinkedList<E> = LockFreeLinkedList<E>().apply {
addAll(elements)
}
@MiraiExperimentalAPI
inline fun <E> lockFreeLinkedListOf(): LockFreeLinkedList<E> = LockFreeLinkedList<E>()
/** /**
* Implementation of lock-free LinkedList. * Implementation of lock-free LinkedList.
* *
* Modifying can be performed concurrently. * Modifying can be performed concurrently.
* Iterating concurrency is guaranteed. // TODO: 2019/12/13 ADD MORE * Iterating concurrency is guaranteed.
*/ */
@MiraiExperimentalAPI class LockFreeLinkedList<E> {
class LockFreeLinkedList<E> : MutableList<E>, RandomAccess {
private val tail: Tail<E> = Tail() private val tail: Tail<E> = Tail()
private val head: Head<E> = Head(tail) private val head: Head<E> = Head(tail)
override fun add(element: E): Boolean { fun add(element: E) {
val node = element.asNode(tail) val node = element.asNode(tail)
while (true) { while (true) {
val tail = head.iterateBeforeFirst { it === tail } // find the last node. val tail = head.iterateBeforeFirst { it === tail } // find the last node.
if (tail.nextNodeRef.compareAndSet(this.tail, node)) { // ensure the last node is the last node if (tail.nextNodeRef.compareAndSet(this.tail, node)) { // ensure the last node is the last node
return true return
}
} }
} }
override fun toString(): String = "[" + buildString {
this@LockFreeLinkedList.forEach {
append(it)
append(", ")
} }
}.dropLast(2) + "]"
internal fun getLinkStucture(): String = buildString { @Suppress("unused")
internal fun getLinkStructure(): String = buildString {
head.childIterateReturnsLastSatisfying<Node<*>>({ head.childIterateReturnsLastSatisfying<Node<*>>({
append(it.toString()) append(it.toString())
append("->") append("->")
it.nextNode it.nextNode
}, { it !is Tail }) }, { it !is Tail })
}.let { }.dropLast(2)
if (it.lastIndex > 0) {
it.substring(0..it.lastIndex - 2)
} else it
}
override fun remove(element: E): Boolean { fun remove(element: E): Boolean {
while (true) { while (true) {
val before = head.iterateBeforeNodeValue(element) val before = head.iterateBeforeNodeValue(element)
val toRemove = before.nextNode val toRemove = before.nextNode
val next = toRemove.nextNode
if (toRemove === tail) { if (toRemove === tail) {
return false return false
} }
if (toRemove.nodeValue === null) {
continue
}
toRemove.nodeValue = null // logically remove: all the operations will recognize this node invalid
if (before.nextNodeRef.compareAndSet(toRemove, next)) { var next: Node<E> = toRemove.nextNode
while (next !== tail && next.nodeValue === null) {
next = next.nextNode
}
if (before.nextNodeRef.compareAndSet(toRemove, next)) {// physically remove: try to fix the link
return true return true
} }
} }
} }
val size: Int get() = head.countChildIterate<Node<E>>({ it.nextNodeRef.value }, { it !is Tail }) - 1 // empty head is always included
operator fun contains(element: E): Boolean = head.iterateBeforeNodeValue(element) !== tail
@Suppress("unused")
fun containsAll(elements: Collection<E>): Boolean = elements.all { contains(it) }
fun isEmpty(): Boolean = head.allMatching { it.isValidElementNode().not() }
fun forEach(block: (E) -> Unit) {
var node: Node<E> = head
while (true) {
if (node === tail) return
node.letIfNotnull(block)
node = node.nextNode
}
}
fun addAll(elements: Collection<E>) = elements.forEach { add(it) }
@Suppress("unused")
fun clear() {
val first = head.nextNode
head.nextNode = tail
first.childIterateReturnFirstUnsatisfying({
val n = it.nextNode
it.nextNode = tail
it.nodeValue = null
n
}, { it !== tail }) // clear the link structure, help GC.
}
@Suppress("unused")
fun removeAll(elements: Collection<E>): Boolean = elements.all { remove(it) }
/*
private fun removeNode(node: Node<E>): Boolean { private fun removeNode(node: Node<E>): Boolean {
if (node == tail) { if (node == tail) {
return false return false
...@@ -78,7 +115,7 @@ class LockFreeLinkedList<E> : MutableList<E>, RandomAccess { ...@@ -78,7 +115,7 @@ class LockFreeLinkedList<E> : MutableList<E>, RandomAccess {
if (toRemove == tail) { // This if (toRemove == tail) { // This
return true return true
} }
toRemove.nodeValue = null // logaically remove first, then all the operations will recognize this node invalid toRemove.nodeValue = null // logically remove first, then all the operations will recognize this node invalid
if (before.nextNodeRef.compareAndSet(toRemove, next)) { // physically remove: try to fix the link if (before.nextNodeRef.compareAndSet(toRemove, next)) { // physically remove: try to fix the link
return true return true
...@@ -86,74 +123,24 @@ class LockFreeLinkedList<E> : MutableList<E>, RandomAccess { ...@@ -86,74 +123,24 @@ class LockFreeLinkedList<E> : MutableList<E>, RandomAccess {
} }
} }
override val size: Int fun removeAt(index: Int): E {
get() = head.countChildIterate<Node<E>>({ it.nextNodeRef.value }, { it !is Tail }) - 1 // empty head is always included require(index >= 0) { "index must be >= 0" }
val nodeBeforeIndex = head.iterateValidNodeNTimes(index)
override operator fun contains(element: E): Boolean = head.iterateBeforeNodeValue(element) !== tail val value = nodeBeforeIndex.nodeValue
if (value === null) noSuchElement()
override fun containsAll(elements: Collection<E>): Boolean = elements.all { contains(it) } removeNode(nodeBeforeIndex)
return value
override operator fun get(index: Int): E {
require(index >= 0) { "Index must be >= 0" }
var i = index + 1 // 1 for head
return head.iterateStopOnFirst { i-- == 0 }.nodeValueRef.value ?: noSuchElement()
}
override fun indexOf(element: E): Int {
var i = -1 // head
if (!head.iterateStopOnFirst {
i++
it.nodeValueRef.value == element
}.isValidElementNode()) {
return -1
}
return i - 1 // iteration is stopped at the next node
}
override fun isEmpty(): Boolean = head.allMatching { it.nodeValueRef.value == null }
/**
* Create a concurrent-unsafe iterator
*/
override operator fun iterator(): MutableIterator<E> = object : MutableIterator<E> {
var currentNode: Node<E>
get() = currentNoderef.value
set(value) {
currentNoderef.value = value
} }
private val currentNoderef: AtomicRef<Node<E>> = atomic(head) // concurrent compatibility operator fun set(index: Int, element: E): E {
/**
* Check if
*
* **Notice That:**
* if `hasNext` returned `true`, then the last remaining element is removed concurrently,
* [next] will produce a [NoSuchElementException]
*/
override fun hasNext(): Boolean = !currentNode.iterateStopOnFirst { it.isValidElementNode() }.isTail()
/**
* Iterate until the next node is not
*/
override fun next(): E {
while (true) { while (true) {
val next = currentNode.nextNode val nodeAtIndex = head.iterateValidNodeNTimes(index + 1)
if (next.isTail()) noSuchElement() val originalValue = nodeAtIndex.nodeValue
if (originalValue === null) noSuchElement() // this node has been removed.
currentNode = next if (!nodeAtIndex.nodeValueRef.compareAndSet(null, element)) { // with concurrent compatibility
continue
val nodeValue = next.nodeValue
if (nodeValue != null) { // next node is not removed, that's what we want
return nodeValue
} // or try again
}
}
override fun remove() {
if (!removeNode(currentNode)) { // search from head onto the node, concurrent compatibility
noSuchElement()
} }
return originalValue
} }
} }
...@@ -169,10 +156,8 @@ class LockFreeLinkedList<E> : MutableList<E>, RandomAccess { ...@@ -169,10 +156,8 @@ class LockFreeLinkedList<E> : MutableList<E>, RandomAccess {
* 4. Repeat 2,3 until the `tail` is reached. * 4. Repeat 2,3 until the `tail` is reached.
* *
* Concurrent changes may influence the result. * Concurrent changes may influence the result.
* While searching,
*
*/ */
override fun lastIndexOf(element: E): Int { fun lastIndexOf(element: E): Int {
var lastMatching: Node<E> = head var lastMatching: Node<E> = head
var searchStartingFrom: Node<E> = lastMatching var searchStartingFrom: Node<E> = lastMatching
var index = 0 // accumulated index from each search var index = 0 // accumulated index from each search
...@@ -188,7 +173,7 @@ class LockFreeLinkedList<E> : MutableList<E>, RandomAccess { ...@@ -188,7 +173,7 @@ class LockFreeLinkedList<E> : MutableList<E>, RandomAccess {
searchStartingFrom = got.nextNode searchStartingFrom = got.nextNode
index += timesOnThisTurn index += timesOnThisTurn
if (!got.isRemoved()) lastMatching = got //record the lastMatching only if got is not removed. if (!got.isRemoved()) lastMatching = got
} }
if (!lastMatching.isValidElementNode()) { if (!lastMatching.isValidElementNode()) {
...@@ -198,61 +183,257 @@ class LockFreeLinkedList<E> : MutableList<E>, RandomAccess { ...@@ -198,61 +183,257 @@ class LockFreeLinkedList<E> : MutableList<E>, RandomAccess {
return index return index
} }
*/
/*
override fun listIterator(): MutableListIterator<E> = listIterator0(0) override fun listIterator(): MutableListIterator<E> = listIterator0(0)
override fun listIterator(index: Int): MutableListIterator<E> = listIterator0(index) override fun listIterator(index: Int): MutableListIterator<E> = listIterator0(index)
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
internal inline fun listIterator0(index: Int): MutableListIterator<E> { private inline fun listIterator0(index: Int): MutableListIterator<E> {
TODO() var first: Node<E> = head
repeat(index) {
first = first.nextNode
if (first === tail) noSuchElement()
}
return object : MutableListIterator<E> {
var currentNode: Node<E>
get() = currentNodeRef.value
set(value) {
currentNodeRef.value = value
} }
override fun subList(fromIndex: Int, toIndex: Int): MutableList<E> { private val currentNodeRef: AtomicRef<Node<E>> = atomic(first) // concurrent compatibility
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
override fun nextIndex(): Int = indexOfNode(currentNode)
// region previous
var previousNode: Node<E>
get() = previousNodeRef.value
set(value) {
previousNodeRef.value = value
}
private val previousNodeRef: AtomicRef<Node<E>> = atomic(head) // concurrent compatibility
private val previousNodeIndexRef: AtomicInt = atomic(-1) // concurrent compatibility
private val currentNodeAtTheMomentWhenPreviousNodeIsUpdated: AtomicRef<Node<E>> = atomic(currentNode)
override fun hasPrevious(): Boolean = previousIndex() == -1
private fun updatePrevious(): Boolean {
while (true) {
val localNodeAtTheMomentWhenPreviousNodeIsUpdated = currentNode
var i = -1 // head
var lastSatisfying: Node<E>? = null
val foundNode = currentNode.childIterateReturnsLastSatisfying({ it.nextNode }, {
i++
if (it.isValidElementNode()) {
lastSatisfying = it
} }
it != currentNode
})
if (localNodeAtTheMomentWhenPreviousNodeIsUpdated !== currentNode) {
continue // current is concurrently changed, must retry
}
override fun add(index: Int, element: E) { if (!foundNode.isValidElementNode()) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. // Current node is not found in the list, meaning it had been removed concurrently
previousNode = head
previousNodeIndexRef.value = -1
return false
} }
override fun addAll(index: Int, elements: Collection<E>): Boolean { if (lastSatisfying === null) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. // All the previous nodes are logically removed.
previousNode = head
previousNodeIndexRef.value = -1
return false
} }
override fun addAll(elements: Collection<E>): Boolean { currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value = localNodeAtTheMomentWhenPreviousNodeIsUpdated
elements.forEach { add(it) } previousNode = lastSatisfying!! // false positive nullable warning
previousNodeIndexRef.value = i
return true return true
} }
}
override fun clear() { override fun previous(): E {
head.nextNode = tail if (currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value == head) {
// node list have been changed.
if (!updatePrevious()) noSuchElement()
}
while (true) {
val value = previousNode.nodeValue
if (value != null) {
currentNode = previousNode
currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value == head
return value
} else if (!updatePrevious()) noSuchElement()
}
}
// TODO: 2019/12/13 check ? override fun previousIndex(): Int {
if (currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value == head) {
// node list have been changed.
if (!updatePrevious()) noSuchElement()
}
while (true) {
val value = previousNodeIndexRef.value
if (value != -1) return value
else if (!updatePrevious()) noSuchElement()
}
} }
// endregion
override fun removeAll(elements: Collection<E>): Boolean { override fun add(element: E) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. val toAdd = element.asNode(tail)
while (true) {
val next = currentNode.nextNode
toAdd.nextNode = next
if (currentNode.nextNodeRef.compareAndSet(next, toAdd)) { // ensure the link is not changed
currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value = head
return
}
}
} }
override fun removeAt(index: Int): E { override fun hasNext(): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. if (currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value == head) {
// node list have been changed.
if (!updatePrevious()) noSuchElement()
}
return currentNode.nextNode !== tail
} }
override fun retainAll(elements: Collection<E>): Boolean { override fun next(): E {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. while (true) {
if (currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value == head) {
// node list have been changed.
if (!updatePrevious()) noSuchElement()
}
val nextNodeValue = currentNode.nextNode.nodeValue
if (nextNodeValue !== null) {
currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value = head
return nextNodeValue
}
}
} }
override operator fun set(index: Int, element: E): E { override fun remove() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. if (!removeNode(currentNode)) { // search from head onto the node, concurrent compatibility
noSuchElement()
}
currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value = head
} }
override fun set(element: E) {
if (currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value == head) {
// node list have been changed.
if (!updatePrevious()) noSuchElement()
}
// NO INLINE: currently exceptions thrown in a inline function cannot be traced }
private fun noSuchElement(): Nothing = throw NoSuchElementException()
}
}
override fun subList(fromIndex: Int, toIndex: Int): MutableList<E> {
}
*/
/*
operator fun get(index: Int): E {
require(index >= 0) { "Index must be >= 0" }
var i = index + 1 // 1 for head
return head.iterateStopOnFirst { i-- == 0 }.nodeValue ?: noSuchElement()
}
fun indexOf(element: E): Int {
var i = -1 // head
if (!head.iterateStopOnFirst {
i++
it.nodeValue == element
}.isValidElementNode()) {
return -1
}
return i - 1 // iteration is stopped at the next node
}
private fun indexOfNode(node: Node<E>): Int {
var i = -1 // head
if (!head.iterateStopOnFirst {
i++
it == node
}.isValidElementNode()) {
return -1
}
return i - 1 // iteration is stopped at the next node
}
operator fun iterator(): MutableIterator<E> = object : MutableIterator<E> {
var currentNode: Node<E>
get() = currentNodeRef.value
set(value) {
currentNodeRef.value = value
}
private val currentNodeRef: AtomicRef<Node<E>> = atomic(head) // concurrent compatibility
/**
* Check if
*
* **Notice That:**
* if `hasNext` returned `true`, then the last remaining element is removed concurrently,
* [next] will produce a [NoSuchElementException]
*/
override fun hasNext(): Boolean = !currentNode.iterateStopOnFirst { it.isValidElementNode() }.isTail()
/**
* Iterate until the next node is not
*/
override fun next(): E {
while (true) {
val next = currentNode.nextNode
if (next.isTail()) noSuchElement()
currentNode = next
val nodeValue = next.nodeValue
if (nodeValue != null) { // next node is not removed, that's what we want
return nodeValue
} // or try again
}
}
override fun remove() {
if (!removeNode(currentNode)) { // search from head onto the node, concurrent compatibility
noSuchElement()
}
}
}
*/
}
/**
* Returns a [List] containing all the elements in [this] in the same order
*/
fun <E> LockFreeLinkedList<E>.toList(): List<E> = toMutableList()
/**
* Returns a [MutableList] containing all the elements in [this] in the same order
*/
fun <E> LockFreeLinkedList<E>.toMutableList(): MutableList<E> {
val list = mutableListOf<E>()
this.forEach { list.add(it) }
return list
} }
// region internal
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
private inline fun <E> E.asNode(nextNode: Node<E>): Node<E> = Node(nextNode).apply { nodeValueRef.value = this@asNode } private inline fun <E> E.asNode(nextNode: Node<E>): Node<E> = Node(nextNode).apply { nodeValueRef.value = this@asNode }
...@@ -280,7 +461,7 @@ private inline fun <N : Node<*>> N.childIterateReturnsLastSatisfying(iterator: ( ...@@ -280,7 +461,7 @@ private inline fun <N : Node<*>> N.childIterateReturnsLastSatisfying(iterator: (
* Self-iterate using the [iterator], until [mustBeTrue] returns `false`. * Self-iterate using the [iterator], until [mustBeTrue] returns `false`.
* Returns the element at the first time when the [mustBeTrue] returns `false` * Returns the element at the first time when the [mustBeTrue] returns `false`
*/ */
private inline fun <E> E.childIterateReturnFirstUnsitisfying(iterator: (E) -> E, mustBeTrue: (E) -> Boolean): E { private inline fun <E> E.childIterateReturnFirstUnsatisfying(iterator: (E) -> E, mustBeTrue: (E) -> Boolean): E {
if (!mustBeTrue(this)) return this if (!mustBeTrue(this)) return this
var value: E = this var value: E = this
...@@ -318,14 +499,12 @@ private inline fun <E> E.countChildIterate(iterator: (E) -> E, mustBeTrue: (E) - ...@@ -318,14 +499,12 @@ private inline fun <E> E.countChildIterate(iterator: (E) -> E, mustBeTrue: (E) -
private class Head<E>( private class Head<E>(
nextNode: Node<E> nextNode: Node<E>
) : Node<E>(nextNode) { ) : Node<E>(nextNode)
}
private open class Node<E>( private open class Node<E>(
nextNode: Node<E>? nextNode: Node<E>?
) { ) {
internal val id: Int = nextId(); internal val id: Int = nextId()
companion object { companion object {
private val idCount = atomic(0) private val idCount = atomic(0)
...@@ -349,6 +528,11 @@ private open class Node<E>( ...@@ -349,6 +528,11 @@ private open class Node<E>(
@Suppress("LeakingThis") @Suppress("LeakingThis")
val nextNodeRef: AtomicRef<Node<E>> = atomic(nextNode ?: this) val nextNodeRef: AtomicRef<Node<E>> = atomic(nextNode ?: this)
inline fun <R> letIfNotnull(block: (E) -> R): R? {
val value = this.nodeValue
return if (value !== null) block(value) else null
}
/** /**
* Short cut for accessing [nextNodeRef] * Short cut for accessing [nextNodeRef]
*/ */
...@@ -360,38 +544,10 @@ private open class Node<E>( ...@@ -360,38 +544,10 @@ private open class Node<E>(
/** /**
* Returns the former node of the last node whence [filter] returnes true * Returns the former node of the last node whence [filter] returns true
*/ */
inline fun iterateBeforeFirst(filter: (Node<E>) -> Boolean): Node<E> = inline fun iterateBeforeFirst(filter: (Node<E>) -> Boolean): Node<E> =
this.childIterateReturnsLastSatisfying<Node<E>>({ it.nextNode }, { !filter(it) }) this.childIterateReturnsLastSatisfying({ it.nextNode }, { !filter(it) })
/**
* Returns the last node whence [filter] returnes true.
*/
inline fun iterateStopOnFirst(filter: (Node<E>) -> Boolean): Node<E> =
iterateBeforeFirst(filter).nextNode
/**
* Returns the former node of next node whose value is not null.
* [Tail] is returned if no node is found
*/
@Suppress("NOTHING_TO_INLINE")
inline fun iterateBeforeNotnull(): Node<E> = iterateBeforeFirst { it.nodeValue != null }
/**
* Returns the next node which is not [Tail] or [Head] and with a notnull value.
* [Tail] is returned if no node is found
*/
@Suppress("NOTHING_TO_INLINE")
inline fun nextValidElement(): Node<E> = this.iterateBeforeFirst { !it.isValidElementNode() }
/**
* Returns the next node whose value is not null.
* [Tail] is returned if no node is found
*/
@Suppress("NOTHING_TO_INLINE")
inline fun nextNotnull(): Node<E> = this.iterateBeforeFirst { it.nodeValueRef.value == null }
/** /**
* Check if all the node which is not [Tail] matches the [condition] * Check if all the node which is not [Tail] matches the [condition]
...@@ -399,7 +555,7 @@ private open class Node<E>( ...@@ -399,7 +555,7 @@ private open class Node<E>(
* Head, which is this, is also being tested. * Head, which is this, is also being tested.
* [Tail], is not being tested. * [Tail], is not being tested.
*/ */
inline fun allMatching(condition: (Node<E>) -> Boolean): Boolean = this.childIterateReturnsLastSatisfying<Node<E>>({ it.nextNode }, condition) !is Tail inline fun allMatching(condition: (Node<E>) -> Boolean): Boolean = this.childIterateReturnsLastSatisfying({ it.nextNode }, condition) !is Tail
/** /**
* Stop on and returns the former element of the element that is [equals] to the [element] * Stop on and returns the former element of the element that is [equals] to the [element]
...@@ -408,21 +564,10 @@ private open class Node<E>( ...@@ -408,21 +564,10 @@ private open class Node<E>(
*/ */
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun iterateBeforeNodeValue(element: E): Node<E> = this.iterateBeforeFirst { it.nodeValueRef.value == element } inline fun iterateBeforeNodeValue(element: E): Node<E> = this.iterateBeforeFirst { it.nodeValueRef.value == element }
/**
* Stop on and returns the element that is [equals] to the [element]
*
* E.g.: for `head <- 1 <- 2 <- 3 <- tail`, `iterateStopOnNodeValue(2)` returns the node whose value is 2
*/
@Suppress("NOTHING_TO_INLINE")
inline fun iterateStopOnNodeValue(element: E): Node<E> = this.iterateBeforeNodeValue(element).nextNode
} }
private open class Tail<E> : Node<E>(null) private open class Tail<E> : Node<E>(null)
@Suppress("unused")
private fun <E> AtomicRef<out Node<out E>>.getNodeValue(): E? = if (this.value is Tail) null else this.value.nodeValueRef.value
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
private inline fun Node<*>.isValidElementNode(): Boolean = !isHead() && !isTail() && !isRemoved() private inline fun Node<*>.isValidElementNode(): Boolean = !isHead() && !isTail() && !isRemoved()
...@@ -434,3 +579,5 @@ private inline fun Node<*>.isTail(): Boolean = this is Tail ...@@ -434,3 +579,5 @@ private inline fun Node<*>.isTail(): Boolean = this is Tail
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
private inline fun Node<*>.isRemoved(): Boolean = this.nodeValue == null private inline fun Node<*>.isRemoved(): Boolean = this.nodeValue == null
// en dregion
\ No newline at end of file
...@@ -4,17 +4,18 @@ package net.mamoe.mirai.utils ...@@ -4,17 +4,18 @@ package net.mamoe.mirai.utils
import kotlinx.coroutines.* import kotlinx.coroutines.*
import net.mamoe.mirai.test.shouldBeEqualTo import net.mamoe.mirai.test.shouldBeEqualTo
import net.mamoe.mirai.test.shouldBeFalse
import net.mamoe.mirai.test.shouldBeTrue import net.mamoe.mirai.test.shouldBeTrue
import org.junit.Test import org.junit.Test
import kotlin.system.exitProcess import kotlin.system.exitProcess
import kotlin.test.* import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@MiraiExperimentalAPI @MiraiExperimentalAPI
internal class LockFreeLinkedListTest { internal class LockFreeLinkedListTest {
init { init {
GlobalScope.launch { GlobalScope.launch {
delay(5000) delay(30 * 1000)
exitProcess(-100) exitProcess(-100)
} }
} }
...@@ -27,26 +28,41 @@ internal class LockFreeLinkedListTest { ...@@ -27,26 +28,41 @@ internal class LockFreeLinkedListTest {
list.add(3) list.add(3)
list.add(4) list.add(4)
assertEquals(list[0], 1, "Failed on list[0]") list.size shouldBeEqualTo 4
assertEquals(list[1], 2, "Failed on list[1]")
assertEquals(list[2], 3, "Failed on list[2]")
assertEquals(list[3], 4, "Failed on list[3]")
} }
@Test @Test
fun addAndGetSingleConcurrent() { fun addAndGetConcurrent() = runBlocking {
//withContext(Dispatchers.Default){
val list = LockFreeLinkedList<Int>() val list = LockFreeLinkedList<Int>()
val add = GlobalScope.async { list.concurrentAdd(1000, 10, 1) }
val remove = GlobalScope.async { list.concurrentAdd(1000, 10, 1)
add.join() list.size shouldBeEqualTo 1000 * 10
list.concurrentDo(100, 10) { list.concurrentDo(100, 10) {
remove(1) remove(1).shouldBeTrue()
}
list.size shouldBeEqualTo 1000 * 10 - 100 * 10
//}
} }
@Test
fun addAndGetMassConcurrentAccess() = runBlocking {
val list = LockFreeLinkedList<Int>()
val addJob = async { list.concurrentAdd(5000, 10, 1) }
delay(10) // let addJob fly
if (addJob.isCompleted) {
error("Number of elements are not enough")
} }
runBlocking { list.concurrentDo(1000, 10) {
joinAll(add, remove) remove(1).shouldBeTrue()
} }
assertEquals(1000 * 10 - 100 * 10, list.size) addJob.join()
list.size shouldBeEqualTo 5000 * 10 - 1000 * 10
} }
@Test @Test
...@@ -66,37 +82,32 @@ internal class LockFreeLinkedListTest { ...@@ -66,37 +82,32 @@ internal class LockFreeLinkedListTest {
} }
@Test @Test
fun getSize() { fun addAll() {
val list = lockFreeLinkedListOf(1, 2, 3, 4, 5) val list = LockFreeLinkedList<Int>()
assertEquals(5, list.size) list.addAll(listOf(1, 2, 3, 4, 5))
list.size shouldBeEqualTo 5
val list2 = lockFreeLinkedListOf<Unit>()
assertEquals(0, list2.size)
} }
@Test @Test
fun contains() { fun clear() {
val list = lockFreeLinkedListOf<Int>() val list = LockFreeLinkedList<Int>()
assertFalse { list.contains(0) } list.addAll(listOf(1, 2, 3, 4, 5))
list.size shouldBeEqualTo 5
list.add(0) list.clear()
assertTrue { list.contains(0) } list.size shouldBeEqualTo 0
} }
@UseExperimental(ExperimentalUnsignedTypes::class)
@Test @Test
fun containsAll() { fun withInlineClassElements() {
var list = lockFreeLinkedListOf(1, 2, 3) val list = LockFreeLinkedList<UInt>()
assertTrue { list.containsAll(listOf(1, 2, 3)) } list.addAll(listOf(1u, 2u, 3u, 4u, 5u))
assertTrue { list.containsAll(listOf()) } list.size shouldBeEqualTo 5
list = lockFreeLinkedListOf(1, 2) list.toString() shouldBeEqualTo "[1, 2, 3, 4, 5]"
assertFalse { list.containsAll(listOf(1, 2, 3)) }
list = lockFreeLinkedListOf()
assertTrue { list.containsAll(listOf()) }
assertFalse { list.containsAll(listOf(1)) }
} }
/*
@Test @Test
fun indexOf() { fun indexOf() {
val list: LockFreeLinkedList<Int> = lockFreeLinkedListOf(1, 2, 3, 3) val list: LockFreeLinkedList<Int> = lockFreeLinkedListOf(1, 2, 3, 3)
...@@ -106,15 +117,6 @@ internal class LockFreeLinkedListTest { ...@@ -106,15 +117,6 @@ internal class LockFreeLinkedListTest {
assertEquals(-1, list.indexOf(4)) assertEquals(-1, list.indexOf(4))
} }
@Test
fun isEmpty() {
val list: LockFreeLinkedList<Int> = lockFreeLinkedListOf()
list.isEmpty().shouldBeTrue()
list.add(1)
list.isEmpty().shouldBeFalse()
}
@Test @Test
fun iterator() { fun iterator() {
var list: LockFreeLinkedList<Int> = lockFreeLinkedListOf(2) var list: LockFreeLinkedList<Int> = lockFreeLinkedListOf(2)
...@@ -171,68 +173,16 @@ internal class LockFreeLinkedListTest { ...@@ -171,68 +173,16 @@ internal class LockFreeLinkedListTest {
list.lastIndexOf(4) shouldBeEqualTo 4 list.lastIndexOf(4) shouldBeEqualTo 4
} }
*/
/*
companion object{
@JvmStatic
fun main(vararg args: String) {
LockFreeLinkedListTest().`lastIndexOf of many elements`()
}
}*/
@Test
fun listIterator() {
}
@Test
fun testListIterator() {
}
@Test
fun subList() {
}
@Test
fun testAdd() {
}
@Test
fun addAll() {
}
@Test
fun testAddAll() {
}
@Test
fun clear() {
}
@Test
fun removeAll() {
}
@Test
fun removeAt() {
}
@Test
fun retainAll() {
}
@Test
fun set() {
}
} }
internal fun withTimeoutBlocking(timeout: Long = 500L, block: suspend () -> Unit) = runBlocking { withTimeout(timeout) { block() } }
@MiraiExperimentalAPI @MiraiExperimentalAPI
internal suspend fun <E> LockFreeLinkedList<E>.concurrentAdd(numberOfCoroutines: Int, timesOfAdd: Int, element: E) = internal suspend inline fun <E> LockFreeLinkedList<E>.concurrentAdd(numberOfCoroutines: Int, timesOfAdd: Int, element: E) =
concurrentDo(numberOfCoroutines, timesOfAdd) { add(element) } concurrentDo(numberOfCoroutines, timesOfAdd) { add(element) }
@MiraiExperimentalAPI @MiraiExperimentalAPI
internal suspend fun <E : LockFreeLinkedList<*>> E.concurrentDo(numberOfCoroutines: Int, timesOfAdd: Int, todo: E.() -> Unit) = coroutineScope { internal suspend inline fun <E : LockFreeLinkedList<*>> E.concurrentDo(numberOfCoroutines: Int, timesOfAdd: Int, crossinline todo: E.() -> Unit) =
coroutineScope {
repeat(numberOfCoroutines) { repeat(numberOfCoroutines) {
launch { launch {
repeat(timesOfAdd) { repeat(timesOfAdd) {
...@@ -240,4 +190,4 @@ internal suspend fun <E : LockFreeLinkedList<*>> E.concurrentDo(numberOfCoroutin ...@@ -240,4 +190,4 @@ internal suspend fun <E : LockFreeLinkedList<*>> E.concurrentDo(numberOfCoroutin
} }
} }
} }
} }
\ No newline at end of file \ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment