blob: dde4b2f69de2677af2e42acc0f064b9b8a20bb2a [file] [log] [blame]
Roman Elizarovf16fd272017-02-07 11:26:00 +03001/*
Roman Elizarov1f74a2d2018-06-29 19:19:45 +03002 * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
Roman Elizarovf16fd272017-02-07 11:26:00 +03003 */
4
Roman Elizarov0950dfa2018-07-13 10:33:25 +03005package kotlinx.coroutines.internal
Roman Elizarov3754f952017-01-18 20:47:54 +03006
Roman Elizarov0950dfa2018-07-13 10:33:25 +03007import kotlinx.coroutines.TestBase
Roman Elizarov3754f952017-01-18 20:47:54 +03008import org.junit.Test
9import java.util.*
10import java.util.concurrent.atomic.AtomicInteger
11import kotlin.concurrent.thread
Roman Elizarov0950dfa2018-07-13 10:33:25 +030012import kotlin.sequences.buildIterator
Roman Elizarov3754f952017-01-18 20:47:54 +030013
Roman Elizarovfa612f92017-02-07 12:11:16 +030014/**
15 * This stress test has 2 threads adding on one side on list, 2 more threads adding on the other,
16 * and 6 threads iterating and concurrently removing items. The resulting list that is being
17 * stressed is long.
18 */
Roman Elizarovebe18b42017-02-28 17:50:55 +030019class LockFreeLinkedListLongStressTest : TestBase() {
Roman Elizarovee7c0eb2017-02-16 15:29:28 +030020 data class IntNode(val i: Int) : LockFreeLinkedListNode()
21 val list = LockFreeLinkedListHead()
Roman Elizarov3754f952017-01-18 20:47:54 +030022
23 val threads = mutableListOf<Thread>()
Vsevolod Tolstopyatova2d80882018-09-24 19:51:49 +030024 private val nAdded = 10_000_000 // should not stress more, because that'll run out of memory
25 private val nAddThreads = 4 // must be power of 2 (!!!)
26 private val nRemoveThreads = 6
27 private val removeProbability = 0.2
28 private val workingAdders = AtomicInteger(nAddThreads)
Roman Elizarov3754f952017-01-18 20:47:54 +030029
Vsevolod Tolstopyatova2d80882018-09-24 19:51:49 +030030 private fun shallRemove(i: Int) = i and 63 != 42
Roman Elizarov3754f952017-01-18 20:47:54 +030031
32 @Test
33 fun testStress() {
Roman Elizarovd3d335b2017-10-21 17:43:53 +030034 println("--- LockFreeLinkedListLongStressTest")
Roman Elizarov3754f952017-01-18 20:47:54 +030035 for (j in 0 until nAddThreads)
Roman Elizarovee7c0eb2017-02-16 15:29:28 +030036 threads += thread(start = false, name = "adder-$j") {
Roman Elizarov3754f952017-01-18 20:47:54 +030037 for (i in j until nAdded step nAddThreads) {
Roman Elizarov3754f952017-01-18 20:47:54 +030038 list.addLast(IntNode(i))
39 }
Roman Elizarov12f961d2017-02-06 15:44:03 +030040 println("${Thread.currentThread().name} completed")
Roman Elizarov3754f952017-01-18 20:47:54 +030041 workingAdders.decrementAndGet()
42 }
43 for (j in 0 until nRemoveThreads)
44 threads += thread(start = false, name = "remover-$j") {
45 val rnd = Random()
46 do {
47 val lastTurn = workingAdders.get() == 0
48 list.forEach<IntNode> { node ->
49 if (shallRemove(node.i) && (lastTurn || rnd.nextDouble() < removeProbability))
50 node.remove()
51 }
52 } while (!lastTurn)
Roman Elizarov12f961d2017-02-06 15:44:03 +030053 println("${Thread.currentThread().name} completed")
Roman Elizarov3754f952017-01-18 20:47:54 +030054 }
55 println("Starting ${threads.size} threads")
56 for (thread in threads)
57 thread.start()
58 println("Joining threads")
59 for (thread in threads)
60 thread.join()
61 // verification
62 println("Verify result")
63 list.validate()
Roman Elizarov0950dfa2018-07-13 10:33:25 +030064 val expected = iterator {
Roman Elizarovee7c0eb2017-02-16 15:29:28 +030065 for (i in 0 until nAdded)
Roman Elizarov3754f952017-01-18 20:47:54 +030066 if (!shallRemove(i))
67 yield(i)
68 }
69 list.forEach<IntNode> { node ->
70 require(node.i == expected.next())
71 }
72 require(!expected.hasNext())
73 }
74}