/* * Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ package test.utils import kotlin.test.* 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 { @Test fun synchronizedLazy() { val counter = AtomicInteger(0) val lazy = lazy { val value = counter.incrementAndGet() Thread.sleep(16) value } 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 val runs = ConcurrentHashMap() val lock = Any() val initializer = { val value = counter.incrementAndGet() runs += (value to initialized) Thread.sleep(16) initialized = true value } val lazy1 = lazy(lock, initializer) val lazy2 = lazy(lock, initializer) val accessThreads = listOf(lazy1, lazy2).map { thread { it.value } } accessThreads.forEach { it.join() } assertEquals(2, counter.get()) @Suppress("NAME_SHADOWING") for ((counter, initialized) in runs) { assertEquals(initialized, counter == 2, "Expected uninitialized on first, initialized on second call: initialized=$initialized, counter=$counter") } } @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 coreCount = Runtime.getRuntime().availableProcessors() val threads = (coreCount / 2).coerceIn(2..3) val values = Random().let { r -> List(threads) { 100 + r.nextInt(50) } } data class Run(val id: Int, val value: Int) val runs = ConcurrentLinkedQueue() val initializer = { val id = counter.getAndIncrement() val value = values[id] runs += Run(id, value) Thread.sleep(value.toLong()) value } val lazy = lazy(LazyThreadSafetyMode.PUBLICATION, initializer) val barrier = CyclicBarrier(threads) val accessThreads = List(threads) { thread { barrier.await(); lazy.value } } val result = run { while (!lazy.isInitialized()) Thread.sleep(1); lazy.value } accessThreads.forEach { it.join() } assertEquals(threads, counter.get()) assertEquals(result, lazy.value, "Value must not change after isInitialized is set: $lazy, runs: $runs") assertTrue(runs.any { it.value == result }, "Unexpected lazy result value: $result, runs: $runs") } @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" } assertFalse(lazy.isInitialized()) val lazy2 = serializeAndDeserialize(lazy) assertTrue(lazy.isInitialized()) assertTrue(lazy2.isInitialized()) 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" }) } }