| /* |
| * 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<Int, Boolean>() |
| 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<Run>() |
| |
| 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 <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" }) |
| } |
| } |