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:
@@ -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" })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user