diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LockFreeLinkedList.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LockFreeLinkedList.kt index bed669359..05683a812 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LockFreeLinkedList.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LockFreeLinkedList.kt @@ -6,67 +6,104 @@ import kotlinx.atomicfu.AtomicRef import kotlinx.atomicfu.atomic import net.mamoe.mirai.utils.Node.Companion.equals - -@MiraiExperimentalAPI -inline fun lockFreeLinkedListOf(vararg elements: E): LockFreeLinkedList = LockFreeLinkedList().apply { - addAll(elements) -} - -@MiraiExperimentalAPI -inline fun lockFreeLinkedListOf(): LockFreeLinkedList = LockFreeLinkedList() - /** * Implementation of lock-free LinkedList. * * Modifying can be performed concurrently. - * Iterating concurrency is guaranteed. // TODO: 2019/12/13 ADD MORE + * Iterating concurrency is guaranteed. */ -@MiraiExperimentalAPI -class LockFreeLinkedList : MutableList, RandomAccess { +class LockFreeLinkedList { private val tail: Tail = Tail() private val head: Head = Head(tail) - override fun add(element: E): Boolean { + fun add(element: E) { val node = element.asNode(tail) while (true) { 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 - return true + return } } - - } - internal fun getLinkStucture(): String = buildString { + override fun toString(): String = "[" + buildString { + this@LockFreeLinkedList.forEach { + append(it) + append(", ") + } + }.dropLast(2) + "]" + + @Suppress("unused") + internal fun getLinkStructure(): String = buildString { head.childIterateReturnsLastSatisfying>({ append(it.toString()) append("->") it.nextNode }, { it !is Tail }) - }.let { - if (it.lastIndex > 0) { - it.substring(0..it.lastIndex - 2) - } else it - } + }.dropLast(2) - override fun remove(element: E): Boolean { + fun remove(element: E): Boolean { while (true) { val before = head.iterateBeforeNodeValue(element) val toRemove = before.nextNode - val next = toRemove.nextNode if (toRemove === tail) { 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 = 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 } } } + val size: Int get() = head.countChildIterate>({ 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): Boolean = elements.all { contains(it) } + + fun isEmpty(): Boolean = head.allMatching { it.isValidElementNode().not() } + + fun forEach(block: (E) -> Unit) { + var node: Node = head + while (true) { + if (node === tail) return + node.letIfNotnull(block) + node = node.nextNode + } + } + + fun addAll(elements: Collection) = 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): Boolean = elements.all { remove(it) } + + /* + + private fun removeNode(node: Node): Boolean { if (node == tail) { return false @@ -78,7 +115,7 @@ class LockFreeLinkedList : MutableList, RandomAccess { if (toRemove == tail) { // This 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 return true @@ -86,43 +123,264 @@ class LockFreeLinkedList : MutableList, RandomAccess { } } - override val size: Int - get() = head.countChildIterate>({ it.nextNodeRef.value }, { it !is Tail }) - 1 // empty head is always included - - override operator fun contains(element: E): Boolean = head.iterateBeforeNodeValue(element) !== tail - - override fun containsAll(elements: Collection): Boolean = elements.all { contains(it) } - - 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() + fun removeAt(index: Int): E { + require(index >= 0) { "index must be >= 0" } + val nodeBeforeIndex = head.iterateValidNodeNTimes(index) + val value = nodeBeforeIndex.nodeValue + if (value === null) noSuchElement() + removeNode(nodeBeforeIndex) + return value } - override fun indexOf(element: E): Int { + operator fun set(index: Int, element: E): E { + while (true) { + val nodeAtIndex = head.iterateValidNodeNTimes(index + 1) + val originalValue = nodeAtIndex.nodeValue + if (originalValue === null) noSuchElement() // this node has been removed. + if (!nodeAtIndex.nodeValueRef.compareAndSet(null, element)) { // with concurrent compatibility + continue + } + return originalValue + } + } + + /** + * Find the last index of the element in the list that is [equals] to [element], with concurrent compatibility. + * + * For a typical list, say `head <- Node#1(1) <- Node#2(2) <- Node#3(3) <- Node#4(4) <- Node#5(2) <- tail`, + * the procedures of `lastIndexOf(2)` is: + * + * 1. Iterate each element, until 2 is found, accumulate the index found, which is 1 + * 2. Search again from the first matching element, which is Node#2 + * 3. Accumulate the index found. + * 4. Repeat 2,3 until the `tail` is reached. + * + * Concurrent changes may influence the result. + */ + fun lastIndexOf(element: E): Int { + var lastMatching: Node = head + var searchStartingFrom: Node = lastMatching + var index = 0 // accumulated index from each search + + findTheLastMatchingElement@ while (true) { // iterate to find the last matching element. + var timesOnThisTurn = if (searchStartingFrom === head) -1 else 0 // ignore the head + val got = searchStartingFrom.nextNode.iterateBeforeFirst { timesOnThisTurn++; it.nodeValue == element } + // find the first match starting from `searchStartingFrom` + + if (got.isTail()) break@findTheLastMatchingElement // no further elements + check(timesOnThisTurn >= 0) { "Internal check failed: too many times ran: $timesOnThisTurn" } + + searchStartingFrom = got.nextNode + index += timesOnThisTurn + + if (!got.isRemoved()) lastMatching = got + } + + if (!lastMatching.isValidElementNode()) { + // found is invalid means not found + return -1 + } + + return index + } + */ + + /* + override fun listIterator(): MutableListIterator = listIterator0(0) + override fun listIterator(index: Int): MutableListIterator = listIterator0(index) + + @Suppress("NOTHING_TO_INLINE") + private inline fun listIterator0(index: Int): MutableListIterator { + var first: Node = head + repeat(index) { + first = first.nextNode + if (first === tail) noSuchElement() + } + return object : MutableListIterator { + var currentNode: Node + get() = currentNodeRef.value + set(value) { + currentNodeRef.value = value + } + + private val currentNodeRef: AtomicRef> = atomic(first) // concurrent compatibility + + override fun nextIndex(): Int = indexOfNode(currentNode) + + + // region previous + + var previousNode: Node + get() = previousNodeRef.value + set(value) { + previousNodeRef.value = value + } + private val previousNodeRef: AtomicRef> = atomic(head) // concurrent compatibility + private val previousNodeIndexRef: AtomicInt = atomic(-1) // concurrent compatibility + private val currentNodeAtTheMomentWhenPreviousNodeIsUpdated: AtomicRef> = atomic(currentNode) + + override fun hasPrevious(): Boolean = previousIndex() == -1 + + private fun updatePrevious(): Boolean { + while (true) { + val localNodeAtTheMomentWhenPreviousNodeIsUpdated = currentNode + var i = -1 // head + var lastSatisfying: Node? = 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 + } + + if (!foundNode.isValidElementNode()) { + // Current node is not found in the list, meaning it had been removed concurrently + previousNode = head + previousNodeIndexRef.value = -1 + return false + } + + if (lastSatisfying === null) { + // All the previous nodes are logically removed. + previousNode = head + previousNodeIndexRef.value = -1 + return false + } + + currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value = localNodeAtTheMomentWhenPreviousNodeIsUpdated + previousNode = lastSatisfying!! // false positive nullable warning + previousNodeIndexRef.value = i + return true + } + } + + override fun previous(): E { + 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() + } + } + + 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 add(element: E) { + 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 hasNext(): Boolean { + if (currentNodeAtTheMomentWhenPreviousNodeIsUpdated.value == head) { + // node list have been changed. + if (!updatePrevious()) noSuchElement() + } + return currentNode.nextNode !== tail + } + + override fun next(): E { + 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 fun remove() { + 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() + } + + } + + } + } + + override fun subList(fromIndex: Int, toIndex: Int): MutableList { + + } + */ + + /* + 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.nodeValueRef.value == element + it.nodeValue == element }.isValidElementNode()) { return -1 } return i - 1 // iteration is stopped at the next node } - override fun isEmpty(): Boolean = head.allMatching { it.nodeValueRef.value == null } + private fun indexOfNode(node: Node): Int { + var i = -1 // head + if (!head.iterateStopOnFirst { + i++ + it == node + }.isValidElementNode()) { + return -1 + } + return i - 1 // iteration is stopped at the next node + } - /** - * Create a concurrent-unsafe iterator - */ - override operator fun iterator(): MutableIterator = object : MutableIterator { + operator fun iterator(): MutableIterator = object : MutableIterator { var currentNode: Node - get() = currentNoderef.value + get() = currentNodeRef.value set(value) { - currentNoderef.value = value + currentNodeRef.value = value } - private val currentNoderef: AtomicRef> = atomic(head) // concurrent compatibility + private val currentNodeRef: AtomicRef> = atomic(head) // concurrent compatibility /** * Check if @@ -156,103 +414,26 @@ class LockFreeLinkedList : MutableList, RandomAccess { } } } - - /** - * Find the last index of the element in the list that is [equals] to [element], with concurrent compatibility. - * - * For a typical list, say `head <- Node#1(1) <- Node#2(2) <- Node#3(3) <- Node#4(4) <- Node#5(2) <- tail`, - * the procedures of `lastIndexOf(2)` is: - * - * 1. Iterate each element, until 2 is found, accumulate the index found, which is 1 - * 2. Search again from the first matching element, which is Node#2 - * 3. Accumulate the index found. - * 4. Repeat 2,3 until the `tail` is reached. - * - * Concurrent changes may influence the result. - * While searching, - * - */ - override fun lastIndexOf(element: E): Int { - var lastMatching: Node = head - var searchStartingFrom: Node = lastMatching - var index = 0 // accumulated index from each search - - findTheLastMatchingElement@ while (true) { // iterate to find the last matching element. - var timesOnThisTurn = if (searchStartingFrom === head) -1 else 0 // ignore the head - val got = searchStartingFrom.nextNode.iterateBeforeFirst { timesOnThisTurn++; it.nodeValue == element } - // find the first match starting from `searchStartingFrom` - - if (got.isTail()) break@findTheLastMatchingElement // no further elements - check(timesOnThisTurn >= 0) { "Internal check failed: too many times ran: $timesOnThisTurn" } - - searchStartingFrom = got.nextNode - index += timesOnThisTurn - - if (!got.isRemoved()) lastMatching = got //record the lastMatching only if got is not removed. - } - - if (!lastMatching.isValidElementNode()) { - // found is invalid means not found - return -1 - } - - return index - } - - override fun listIterator(): MutableListIterator = listIterator0(0) - override fun listIterator(index: Int): MutableListIterator = listIterator0(index) - - @Suppress("NOTHING_TO_INLINE") - internal inline fun listIterator0(index: Int): MutableListIterator { - TODO() - } - - override fun subList(fromIndex: Int, toIndex: Int): MutableList { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - - override fun add(index: Int, element: E) { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - override fun addAll(index: Int, elements: Collection): Boolean { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - override fun addAll(elements: Collection): Boolean { - elements.forEach { add(it) } - return true - } - - override fun clear() { - head.nextNode = tail - - // TODO: 2019/12/13 check ? - } - - override fun removeAll(elements: Collection): Boolean { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - override fun removeAt(index: Int): E { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - override fun retainAll(elements: Collection): Boolean { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - override operator fun set(index: Int, element: E): E { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - - // NO INLINE: currently exceptions thrown in a inline function cannot be traced - private fun noSuchElement(): Nothing = throw NoSuchElementException() - + */ } +/** + * Returns a [List] containing all the elements in [this] in the same order + */ +fun LockFreeLinkedList.toList(): List = toMutableList() + +/** + * Returns a [MutableList] containing all the elements in [this] in the same order + */ +fun LockFreeLinkedList.toMutableList(): MutableList { + val list = mutableListOf() + this.forEach { list.add(it) } + return list +} + + +// region internal + @Suppress("NOTHING_TO_INLINE") private inline fun E.asNode(nextNode: Node): Node = Node(nextNode).apply { nodeValueRef.value = this@asNode } @@ -280,7 +461,7 @@ private inline fun > N.childIterateReturnsLastSatisfying(iterator: ( * Self-iterate using the [iterator], until [mustBeTrue] returns `false`. * Returns the element at the first time when the [mustBeTrue] returns `false` */ -private inline fun E.childIterateReturnFirstUnsitisfying(iterator: (E) -> E, mustBeTrue: (E) -> Boolean): E { +private inline fun E.childIterateReturnFirstUnsatisfying(iterator: (E) -> E, mustBeTrue: (E) -> Boolean): E { if (!mustBeTrue(this)) return this var value: E = this @@ -318,14 +499,12 @@ private inline fun E.countChildIterate(iterator: (E) -> E, mustBeTrue: (E) - private class Head( nextNode: Node -) : Node(nextNode) { - -} +) : Node(nextNode) private open class Node( nextNode: Node? ) { - internal val id: Int = nextId(); + internal val id: Int = nextId() companion object { private val idCount = atomic(0) @@ -349,6 +528,11 @@ private open class Node( @Suppress("LeakingThis") val nextNodeRef: AtomicRef> = atomic(nextNode ?: this) + inline fun letIfNotnull(block: (E) -> R): R? { + val value = this.nodeValue + return if (value !== null) block(value) else null + } + /** * Short cut for accessing [nextNodeRef] */ @@ -360,38 +544,10 @@ private open class Node( /** - * 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) -> Boolean): Node = - this.childIterateReturnsLastSatisfying>({ it.nextNode }, { !filter(it) }) - - /** - * Returns the last node whence [filter] returnes true. - */ - inline fun iterateStopOnFirst(filter: (Node) -> Boolean): Node = - 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 = 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 = 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 = this.iterateBeforeFirst { it.nodeValueRef.value == null } + this.childIterateReturnsLastSatisfying({ it.nextNode }, { !filter(it) }) /** * Check if all the node which is not [Tail] matches the [condition] @@ -399,7 +555,7 @@ private open class Node( * Head, which is this, is also being tested. * [Tail], is not being tested. */ - inline fun allMatching(condition: (Node) -> Boolean): Boolean = this.childIterateReturnsLastSatisfying>({ it.nextNode }, condition) !is Tail + inline fun allMatching(condition: (Node) -> 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] @@ -408,21 +564,10 @@ private open class Node( */ @Suppress("NOTHING_TO_INLINE") inline fun iterateBeforeNodeValue(element: E): Node = 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 = this.iterateBeforeNodeValue(element).nextNode } private open class Tail : Node(null) -@Suppress("unused") -private fun AtomicRef>.getNodeValue(): E? = if (this.value is Tail) null else this.value.nodeValueRef.value - @Suppress("NOTHING_TO_INLINE") private inline fun Node<*>.isValidElementNode(): Boolean = !isHead() && !isTail() && !isRemoved() @@ -433,4 +578,6 @@ private inline fun Node<*>.isHead(): Boolean = this is Head private inline fun Node<*>.isTail(): Boolean = this is Tail @Suppress("NOTHING_TO_INLINE") -private inline fun Node<*>.isRemoved(): Boolean = this.nodeValue == null \ No newline at end of file +private inline fun Node<*>.isRemoved(): Boolean = this.nodeValue == null + +// en dregion \ No newline at end of file diff --git a/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/utils/LockFreeLinkedListTest.kt b/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/utils/LockFreeLinkedListTest.kt index 1e3e8ed0d..976b65e32 100644 --- a/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/utils/LockFreeLinkedListTest.kt +++ b/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/utils/LockFreeLinkedListTest.kt @@ -4,17 +4,18 @@ package net.mamoe.mirai.utils import kotlinx.coroutines.* import net.mamoe.mirai.test.shouldBeEqualTo -import net.mamoe.mirai.test.shouldBeFalse import net.mamoe.mirai.test.shouldBeTrue import org.junit.Test import kotlin.system.exitProcess -import kotlin.test.* +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue @MiraiExperimentalAPI internal class LockFreeLinkedListTest { init { GlobalScope.launch { - delay(5000) + delay(30 * 1000) exitProcess(-100) } } @@ -27,26 +28,41 @@ internal class LockFreeLinkedListTest { list.add(3) list.add(4) - assertEquals(list[0], 1, "Failed on list[0]") - assertEquals(list[1], 2, "Failed on list[1]") - assertEquals(list[2], 3, "Failed on list[2]") - assertEquals(list[3], 4, "Failed on list[3]") + list.size shouldBeEqualTo 4 } @Test - fun addAndGetSingleConcurrent() { + fun addAndGetConcurrent() = runBlocking { + //withContext(Dispatchers.Default){ val list = LockFreeLinkedList() - val add = GlobalScope.async { list.concurrentAdd(1000, 10, 1) } - val remove = GlobalScope.async { - add.join() - list.concurrentDo(100, 10) { - remove(1) - } + + list.concurrentAdd(1000, 10, 1) + list.size shouldBeEqualTo 1000 * 10 + + list.concurrentDo(100, 10) { + remove(1).shouldBeTrue() } - runBlocking { - joinAll(add, remove) + + list.size shouldBeEqualTo 1000 * 10 - 100 * 10 + //} + } + + @Test + fun addAndGetMassConcurrentAccess() = runBlocking { + val list = LockFreeLinkedList() + + val addJob = async { list.concurrentAdd(5000, 10, 1) } + + delay(10) // let addJob fly + if (addJob.isCompleted) { + error("Number of elements are not enough") } - assertEquals(1000 * 10 - 100 * 10, list.size) + list.concurrentDo(1000, 10) { + remove(1).shouldBeTrue() + } + addJob.join() + + list.size shouldBeEqualTo 5000 * 10 - 1000 * 10 } @Test @@ -65,179 +81,113 @@ internal class LockFreeLinkedListTest { assertEquals(1, list.size) } - @Test - fun getSize() { - val list = lockFreeLinkedListOf(1, 2, 3, 4, 5) - assertEquals(5, list.size) - - val list2 = lockFreeLinkedListOf() - assertEquals(0, list2.size) - } - - @Test - fun contains() { - val list = lockFreeLinkedListOf() - assertFalse { list.contains(0) } - - list.add(0) - assertTrue { list.contains(0) } - } - - @Test - fun containsAll() { - var list = lockFreeLinkedListOf(1, 2, 3) - assertTrue { list.containsAll(listOf(1, 2, 3)) } - assertTrue { list.containsAll(listOf()) } - - list = lockFreeLinkedListOf(1, 2) - assertFalse { list.containsAll(listOf(1, 2, 3)) } - - list = lockFreeLinkedListOf() - assertTrue { list.containsAll(listOf()) } - assertFalse { list.containsAll(listOf(1)) } - } - - @Test - fun indexOf() { - val list: LockFreeLinkedList = lockFreeLinkedListOf(1, 2, 3, 3) - assertEquals(0, list.indexOf(1)) - assertEquals(2, list.indexOf(3)) - - assertEquals(-1, list.indexOf(4)) - } - - @Test - fun isEmpty() { - val list: LockFreeLinkedList = lockFreeLinkedListOf() - list.isEmpty().shouldBeTrue() - - list.add(1) - list.isEmpty().shouldBeFalse() - } - - @Test - fun iterator() { - var list: LockFreeLinkedList = lockFreeLinkedListOf(2) - list.forEach { - it shouldBeEqualTo 2 - } - - list = lockFreeLinkedListOf(1, 2) - list.joinToString { it.toString() } shouldBeEqualTo "1, 2" - - - list = lockFreeLinkedListOf(1, 2) - val iterator = list.iterator() - iterator.remove() - var reached = false - for (i in iterator) { - i shouldBeEqualTo 2 - reached = true - } - reached shouldBeEqualTo true - - list.joinToString { it.toString() } shouldBeEqualTo "2" - iterator.remove() - assertFailsWith { iterator.remove() } - } - - @Test - fun `lastIndexOf of exact 1 match at first`() { - val list: LockFreeLinkedList = lockFreeLinkedListOf(2, 1) - list.lastIndexOf(2) shouldBeEqualTo 0 - } - - @Test - fun `lastIndexOf of exact 1 match`() { - val list: LockFreeLinkedList = lockFreeLinkedListOf(1, 2) - list.lastIndexOf(2) shouldBeEqualTo 1 - } - - @Test - fun `lastIndexOf of multiply matches`() { - val list: LockFreeLinkedList = lockFreeLinkedListOf(1, 2, 2) - list.lastIndexOf(2) shouldBeEqualTo 2 - } - - @Test - fun `lastIndexOf of no match`() { - val list: LockFreeLinkedList = lockFreeLinkedListOf(2) - list.lastIndexOf(3) shouldBeEqualTo -1 - } - - @Test - fun `lastIndexOf of many elements`() { - val list: LockFreeLinkedList = lockFreeLinkedListOf(1, 4, 2, 3, 4, 5) - 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() { + val list = LockFreeLinkedList() + list.addAll(listOf(1, 2, 3, 4, 5)) + list.size shouldBeEqualTo 5 } @Test fun clear() { + val list = LockFreeLinkedList() + list.addAll(listOf(1, 2, 3, 4, 5)) + list.size shouldBeEqualTo 5 + list.clear() + list.size shouldBeEqualTo 0 + } + + @UseExperimental(ExperimentalUnsignedTypes::class) + @Test + fun withInlineClassElements() { + val list = LockFreeLinkedList() + list.addAll(listOf(1u, 2u, 3u, 4u, 5u)) + list.size shouldBeEqualTo 5 + + list.toString() shouldBeEqualTo "[1, 2, 3, 4, 5]" + } + + /* + @Test + fun indexOf() { + val list: LockFreeLinkedList = lockFreeLinkedListOf(1, 2, 3, 3) + assertEquals(0, list.indexOf(1)) + assertEquals(2, list.indexOf(3)) + + assertEquals(-1, list.indexOf(4)) } @Test - fun removeAll() { + fun iterator() { + var list: LockFreeLinkedList = lockFreeLinkedListOf(2) + list.forEach { + it shouldBeEqualTo 2 + } + + list = lockFreeLinkedListOf(1, 2) + list.joinToString { it.toString() } shouldBeEqualTo "1, 2" + + + list = lockFreeLinkedListOf(1, 2) + val iterator = list.iterator() + iterator.remove() + var reached = false + for (i in iterator) { + i shouldBeEqualTo 2 + reached = true + } + reached shouldBeEqualTo true + + list.joinToString { it.toString() } shouldBeEqualTo "2" + iterator.remove() + assertFailsWith { iterator.remove() } } @Test - fun removeAt() { + fun `lastIndexOf of exact 1 match at first`() { + val list: LockFreeLinkedList = lockFreeLinkedListOf(2, 1) + list.lastIndexOf(2) shouldBeEqualTo 0 } @Test - fun retainAll() { + fun `lastIndexOf of exact 1 match`() { + val list: LockFreeLinkedList = lockFreeLinkedListOf(1, 2) + list.lastIndexOf(2) shouldBeEqualTo 1 } @Test - fun set() { + fun `lastIndexOf of multiply matches`() { + val list: LockFreeLinkedList = lockFreeLinkedListOf(1, 2, 2) + list.lastIndexOf(2) shouldBeEqualTo 2 } + + @Test + fun `lastIndexOf of no match`() { + val list: LockFreeLinkedList = lockFreeLinkedListOf(2) + list.lastIndexOf(3) shouldBeEqualTo -1 + } + + @Test + fun `lastIndexOf of many elements`() { + val list: LockFreeLinkedList = lockFreeLinkedListOf(1, 4, 2, 3, 4, 5) + list.lastIndexOf(4) shouldBeEqualTo 4 + } + + */ } -internal fun withTimeoutBlocking(timeout: Long = 500L, block: suspend () -> Unit) = runBlocking { withTimeout(timeout) { block() } } - @MiraiExperimentalAPI -internal suspend fun LockFreeLinkedList.concurrentAdd(numberOfCoroutines: Int, timesOfAdd: Int, element: E) = +internal suspend inline fun LockFreeLinkedList.concurrentAdd(numberOfCoroutines: Int, timesOfAdd: Int, element: E) = concurrentDo(numberOfCoroutines, timesOfAdd) { add(element) } @MiraiExperimentalAPI -internal suspend fun > E.concurrentDo(numberOfCoroutines: Int, timesOfAdd: Int, todo: E.() -> Unit) = coroutineScope { - repeat(numberOfCoroutines) { - launch { - repeat(timesOfAdd) { - todo() +internal suspend inline fun > E.concurrentDo(numberOfCoroutines: Int, timesOfAdd: Int, crossinline todo: E.() -> Unit) = + coroutineScope { + repeat(numberOfCoroutines) { + launch { + repeat(timesOfAdd) { + todo() + } } } - } -} \ No newline at end of file + } \ No newline at end of file