blob: 24dfd2324825f26e9ea78afece4d43ab998b1738 [file] [log] [blame]
Roman Elizarov7eb41ef2017-08-10 21:09:26 +03001/*
2 * Copyright 2016-2017 JetBrains s.r.o.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package kotlinx.coroutines.experimental.internal
18
19import kotlinx.atomicfu.LockFreedomTestEnvironment
20import kotlinx.coroutines.experimental.TestBase
21import org.junit.Assert.*
22import org.junit.Test
23import java.util.*
24import java.util.concurrent.atomic.AtomicLong
25import java.util.concurrent.atomic.AtomicReference
26
27/**
28 * This stress test has 4 threads adding randomly to the list and them immediately undoing
29 * this addition by remove, and 4 threads trying to remove nodes from two lists simultaneously (atomically).
30 */
31class LockFreeLinkedListAtomicStressLFTest : TestBase() {
32 private val env = LockFreedomTestEnvironment("LockFreeLinkedListAtomicStressLFTest")
33
34 data class IntNode(val i: Int) : LockFreeLinkedListNode()
35
Roman Elizarove7f9b5f2017-08-16 23:24:07 +030036 private val TEST_DURATION_SEC = 5 * stressTestMultiplier
Roman Elizarov7eb41ef2017-08-10 21:09:26 +030037
38 val nLists = 4
39 val nAdderThreads = 4
40 val nRemoverThreads = 4
41
42 val lists = Array(nLists) { LockFreeLinkedListHead() }
43
44 val undone = AtomicLong()
45 val missed = AtomicLong()
46 val removed = AtomicLong()
47 val error = AtomicReference<Throwable>()
48
49 @Test
50 fun testStress() {
51 repeat(nAdderThreads) { threadId ->
52 val rnd = Random()
53 env.testThread(name = "adder-$threadId") {
54 when (rnd.nextInt(4)) {
55 0 -> {
56 val list = lists[rnd.nextInt(nLists)]
57 val node = IntNode(threadId)
Roman Elizarove1f89db2017-08-16 15:08:30 +030058 addLastOp(list, node)
Roman Elizarov4ecd46b2017-08-15 11:15:08 +030059 randomSpinWaitIntermission()
Roman Elizarove1f89db2017-08-16 15:08:30 +030060 tryRemoveOp(node)
Roman Elizarov7eb41ef2017-08-10 21:09:26 +030061 }
62 1 -> {
63 // just to test conditional add
64 val list = lists[rnd.nextInt(nLists)]
65 val node = IntNode(threadId)
Roman Elizarove1f89db2017-08-16 15:08:30 +030066 addLastIfTrueOp(list, node)
Roman Elizarov4ecd46b2017-08-15 11:15:08 +030067 randomSpinWaitIntermission()
Roman Elizarove1f89db2017-08-16 15:08:30 +030068 tryRemoveOp(node)
Roman Elizarov7eb41ef2017-08-10 21:09:26 +030069 }
70 2 -> {
71 // just to test failed conditional add and burn some time
72 val list = lists[rnd.nextInt(nLists)]
73 val node = IntNode(threadId)
Roman Elizarove1f89db2017-08-16 15:08:30 +030074 addLastIfFalseOp(list, node)
Roman Elizarov7eb41ef2017-08-10 21:09:26 +030075 }
76 3 -> {
77 // add two atomically
78 val idx1 = rnd.nextInt(nLists - 1)
79 val idx2 = idx1 + 1 + rnd.nextInt(nLists - idx1 - 1)
80 check(idx1 < idx2) // that is our global order
81 val list1 = lists[idx1]
82 val list2 = lists[idx2]
83 val node1 = IntNode(threadId)
84 val node2 = IntNode(-threadId - 1)
Roman Elizarove1f89db2017-08-16 15:08:30 +030085 addTwoOp(list1, node1, list2, node2)
Roman Elizarov4ecd46b2017-08-15 11:15:08 +030086 randomSpinWaitIntermission()
Roman Elizarove1f89db2017-08-16 15:08:30 +030087 tryRemoveOp(node1)
Roman Elizarov4ecd46b2017-08-15 11:15:08 +030088 randomSpinWaitIntermission()
Roman Elizarove1f89db2017-08-16 15:08:30 +030089 tryRemoveOp(node2)
Roman Elizarov7eb41ef2017-08-10 21:09:26 +030090 }
91 else -> error("Cannot happen")
92 }
93 }
94 }
95 repeat(nRemoverThreads) { threadId ->
96 val rnd = Random()
97 env.testThread(name = "remover-$threadId") {
98 val idx1 = rnd.nextInt(nLists - 1)
99 val idx2 = idx1 + 1 + rnd.nextInt(nLists - idx1 - 1)
100 check(idx1 < idx2) // that is our global order
101 val list1 = lists[idx1]
102 val list2 = lists[idx2]
Roman Elizarove1f89db2017-08-16 15:08:30 +0300103 removeTwoOp(list1, list2)
Roman Elizarov7eb41ef2017-08-10 21:09:26 +0300104 }
105 }
106 env.performTest(TEST_DURATION_SEC) {
107 val _undone = undone.get()
108 val _missed = missed.get()
109 val _removed = removed.get()
110 println(" Adders undone $_undone node additions")
111 println(" Adders missed $_missed nodes")
112 println("Remover removed $_removed nodes")
113 }
114 error.get()?.let { throw it }
115 assertEquals(missed.get(), removed.get())
116 assertTrue(undone.get() > 0)
117 assertTrue(missed.get() > 0)
118 lists.forEach { it.validate() }
119 }
120
Roman Elizarove1f89db2017-08-16 15:08:30 +0300121 private fun addLastOp(list: LockFreeLinkedListHead, node: IntNode) {
122 list.addLast(node)
123 }
124
125 private fun addLastIfTrueOp(list: LockFreeLinkedListHead, node: IntNode) {
126 assertTrue(list.addLastIf(node, { true }))
127 }
128
129 private fun addLastIfFalseOp(list: LockFreeLinkedListHead, node: IntNode) {
130 assertFalse(list.addLastIf(node, { false }))
131 }
132
133 private fun addTwoOp(list1: LockFreeLinkedListHead, node1: IntNode, list2: LockFreeLinkedListHead, node2: IntNode) {
134 val add1 = list1.describeAddLast(node1)
135 val add2 = list2.describeAddLast(node2)
136 val op = object : AtomicOp<Any?>() {
137 override fun prepare(affected: Any?): Any? =
138 add1.prepare(this) ?:
139 add2.prepare(this)
140
141 override fun complete(affected: Any?, failure: Any?) {
142 add1.complete(this, failure)
143 add2.complete(this, failure)
144 }
145 }
146 assertTrue(op.perform(null) == null)
147 }
148
149 private fun tryRemoveOp(node: IntNode) {
Roman Elizarov7eb41ef2017-08-10 21:09:26 +0300150 if (node.remove())
151 undone.incrementAndGet()
152 else
153 missed.incrementAndGet()
154 }
Roman Elizarove1f89db2017-08-16 15:08:30 +0300155
156 private fun removeTwoOp(list1: LockFreeLinkedListHead, list2: LockFreeLinkedListHead) {
157 val remove1 = list1.describeRemoveFirst()
158 val remove2 = list2.describeRemoveFirst()
159 val op = object : AtomicOp<Any?>() {
160 override fun prepare(affected: Any?): Any? =
161 remove1.prepare(this) ?:
162 remove2.prepare(this)
163
164 override fun complete(affected: Any?, failure: Any?) {
165 remove1.complete(this, failure)
166 remove2.complete(this, failure)
167 }
168 }
169 val success = op.perform(null) == null
170 if (success) removed.addAndGet(2)
171 }
172
Roman Elizarov7eb41ef2017-08-10 21:09:26 +0300173}