diff --git a/libraries/stdlib/test/utils/LazyJVMTest.kt b/libraries/stdlib/test/utils/LazyJVMTest.kt index e6a2668b9cc..899140d2cf0 100644 --- a/libraries/stdlib/test/utils/LazyJVMTest.kt +++ b/libraries/stdlib/test/utils/LazyJVMTest.kt @@ -4,13 +4,14 @@ package test.utils import kotlin.* import kotlin.test.* -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.atomic.AtomicInteger import kotlin.concurrent.thread import test.io.serializeAndDeserialize import java.util.* 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.AtomicInteger class LazyJVMTest { @@ -18,17 +19,28 @@ class LazyJVMTest { val counter = AtomicInteger(0) val lazy = lazy { val value = counter.incrementAndGet() - Thread.sleep(80) + Thread.sleep(16) value } - val accessThreads = List(3) { thread(start = false) { lazy.value } } - accessThreads.forEach { it.start() } + val threads = 3 + val barrier = CyclicBarrier(threads) + val accessThreads = List(threads) { thread { barrier.await(); lazy.value } } accessThreads.forEach { it.join() } 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() { val counter = AtomicInteger(0) var initialized: Boolean = false @@ -38,7 +50,7 @@ class LazyJVMTest { val initializer = { val value = counter.incrementAndGet() runs += (value to initialized) - Thread.sleep(50) + Thread.sleep(16) initialized = true 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() { val counter = AtomicInteger(0) val initialized = AtomicBoolean(false) val threads = 3 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() val initializer = { val id = counter.getAndIncrement() val value = values[id] - runs += Run(id, value, initialized.get()) + runs += Run(value, initialized.get()) Thread.sleep(value.toLong()) initialized.set(true) value } val lazy = lazy(LazyThreadSafetyMode.PUBLICATION, initializer) - val accessThreads = List(threads) { thread(start = false) { lazy.value } } - accessThreads.forEach { it.start() } + val barrier = CyclicBarrier(threads) + val accessThreads = List(threads) { thread { barrier.await(); lazy.value } } val result = run { while (!lazy.isInitialized()) /* wait */; lazy.value } 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() { for(mode in listOf(LazyThreadSafetyMode.SYNCHRONIZED, LazyThreadSafetyMode.PUBLICATION, LazyThreadSafetyMode.NONE)) { val lazy = lazy(mode) { "initialized" } @@ -96,4 +133,38 @@ class LazyJVMTest { assertEquals(lazy.value, lazy2.value) } } + + private fun racyTest( + threads: Int = 3, runs: Int = 5000, + initialize: () -> TState, + access: (TState, runnerIndex: Int) -> TResult, + validate: (List) -> Boolean) { + + val runResult = java.util.Collections.synchronizedList(mutableListOf()) + val invalidResults = mutableListOf>>() + 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" }) + } } \ No newline at end of file