Add racing version of each Lazy test

Run several concurrent accesses to a lazy value
many times and validate invariants.
This commit is contained in:
Ilya Gorbunov
2017-12-22 05:01:45 +03:00
parent 4827aadcfd
commit f468990f97
+81 -10
View File
@@ -4,13 +4,14 @@ package test.utils
import kotlin.* import kotlin.*
import kotlin.test.* import kotlin.test.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicInteger
import kotlin.concurrent.thread import kotlin.concurrent.thread
import test.io.serializeAndDeserialize import test.io.serializeAndDeserialize
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CyclicBarrier
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
class LazyJVMTest { class LazyJVMTest {
@@ -18,17 +19,28 @@ class LazyJVMTest {
val counter = AtomicInteger(0) val counter = AtomicInteger(0)
val lazy = lazy { val lazy = lazy {
val value = counter.incrementAndGet() val value = counter.incrementAndGet()
Thread.sleep(80) Thread.sleep(16)
value value
} }
val accessThreads = List(3) { thread(start = false) { lazy.value } } val threads = 3
accessThreads.forEach { it.start() } val barrier = CyclicBarrier(threads)
val accessThreads = List(threads) { thread { barrier.await(); lazy.value } }
accessThreads.forEach { it.join() } accessThreads.forEach { it.join() }
assertEquals(1, counter.get()) assertEquals(1, counter.get())
} }
@Test fun synchronizedLazyRace() {
racyTest(initialize = {
val counter = AtomicInteger(0)
lazy { counter.incrementAndGet() }
},
access = { lazy, _ -> lazy.value },
validate = { result -> result.all { it == 1 } }
)
}
@Test fun externallySynchronizedLazy() { @Test fun externallySynchronizedLazy() {
val counter = AtomicInteger(0) val counter = AtomicInteger(0)
var initialized: Boolean = false var initialized: Boolean = false
@@ -38,7 +50,7 @@ class LazyJVMTest {
val initializer = { val initializer = {
val value = counter.incrementAndGet() val value = counter.incrementAndGet()
runs += (value to initialized) runs += (value to initialized)
Thread.sleep(50) Thread.sleep(16)
initialized = true initialized = true
value value
} }
@@ -55,26 +67,45 @@ class LazyJVMTest {
} }
} }
@Test fun externallySynchronizedLazyRace() {
val threads = 3
racyTest(threads,
initialize = {
val counter = AtomicInteger(0)
var initialized = false
val initializer = {
(counter.incrementAndGet() to initialized).also {
initialized = true
}
}
val lock = Any()
List(threads) { lazy(lock, initializer) }
},
access = { lazies, runnerIndex -> lazies[runnerIndex].value },
validate = { result -> result.all { (id, initialized) -> initialized == (id != 1)} })
}
@Test fun publishOnceLazy() { @Test fun publishOnceLazy() {
val counter = AtomicInteger(0) val counter = AtomicInteger(0)
val initialized = AtomicBoolean(false) val initialized = AtomicBoolean(false)
val threads = 3 val threads = 3
val values = Random().let { r -> List(threads) { 50 + r.nextInt(50) } } val values = Random().let { r -> List(threads) { 50 + r.nextInt(50) } }
data class Run(val id: Int, val value: Int, val initialized: Boolean) data class Run(val value: Int, val initialized: Boolean)
val runs = ConcurrentLinkedQueue<Run>() val runs = ConcurrentLinkedQueue<Run>()
val initializer = { val initializer = {
val id = counter.getAndIncrement() val id = counter.getAndIncrement()
val value = values[id] val value = values[id]
runs += Run(id, value, initialized.get()) runs += Run(value, initialized.get())
Thread.sleep(value.toLong()) Thread.sleep(value.toLong())
initialized.set(true) initialized.set(true)
value value
} }
val lazy = lazy(LazyThreadSafetyMode.PUBLICATION, initializer) val lazy = lazy(LazyThreadSafetyMode.PUBLICATION, initializer)
val accessThreads = List(threads) { thread(start = false) { lazy.value } } val barrier = CyclicBarrier(threads)
accessThreads.forEach { it.start() } val accessThreads = List(threads) { thread { barrier.await(); lazy.value } }
val result = run { while (!lazy.isInitialized()) /* wait */; lazy.value } val result = run { while (!lazy.isInitialized()) /* wait */; lazy.value }
accessThreads.forEach { it.join() } accessThreads.forEach { it.join() }
@@ -86,6 +117,12 @@ class LazyJVMTest {
} }
} }
@Test fun publishOnceLazyRace() {
racyTest(initialize = { lazy(LazyThreadSafetyMode.PUBLICATION) { Thread.currentThread().id } },
access = { lazy, _ -> lazy.value },
validate = { result -> result.all { v -> v == result[0] } } )
}
@Test fun lazyInitializationForcedOnSerialization() { @Test fun lazyInitializationForcedOnSerialization() {
for(mode in listOf(LazyThreadSafetyMode.SYNCHRONIZED, LazyThreadSafetyMode.PUBLICATION, LazyThreadSafetyMode.NONE)) { for(mode in listOf(LazyThreadSafetyMode.SYNCHRONIZED, LazyThreadSafetyMode.PUBLICATION, LazyThreadSafetyMode.NONE)) {
val lazy = lazy(mode) { "initialized" } val lazy = lazy(mode) { "initialized" }
@@ -96,4 +133,38 @@ class LazyJVMTest {
assertEquals(lazy.value, lazy2.value) assertEquals(lazy.value, lazy2.value)
} }
} }
private fun <TState : Any, TResult> racyTest(
threads: Int = 3, runs: Int = 5000,
initialize: () -> TState,
access: (TState, runnerIndex: Int) -> TResult,
validate: (List<TResult>) -> Boolean) {
val runResult = java.util.Collections.synchronizedList(mutableListOf<TResult>())
val invalidResults = mutableListOf<Pair<Int, List<TResult>>>()
lateinit var state: TState
var runId = -1
val barrier = CyclicBarrier(threads) {
if (runId >= 0) {
if (!validate(runResult))
invalidResults.add(runId to runResult.toList())
runResult.clear()
}
state = initialize()
runId += 1
}
val runners = List(threads) { index -> thread {
barrier.await()
repeat(runs) {
runResult += access(state, index)
barrier.await()
}
}}
runners.forEach { it.join() }
assertTrue(invalidResults.isEmpty(), invalidResults.joinToString("\n") { (index, result) -> "At run #$index: $result" })
}
} }