[K/N] Implement Lazy for new mm

This commit is contained in:
Pavel Kunyavskiy
2021-07-12 11:51:52 +03:00
committed by Space
parent 2f2e608502
commit d88a665fa8
4 changed files with 168 additions and 35 deletions
@@ -8,53 +8,93 @@ package runtime.workers.lazy1
import kotlin.test.*
import kotlin.native.concurrent.*
class Lazy {
private var y = 20
class Lazy(mode: LazyThreadSafetyMode) {
val x = 17
val self by lazy { this }
val recursion: Int by lazy {
val self by lazy(mode) { this }
val recursion: Int by lazy(mode) {
if (x < 17) 42 else recursion
}
val freezer: Int by lazy {
val finiteRecursion : Int by lazy(mode) {
if (y < 17) 42 else {
y -= 1
finiteRecursion + 1
}
}
val freezer: Int by lazy(mode) {
freeze()
42
}
val thrower: String by lazy {
val thrower: String by lazy(mode) {
if (x < 100) throw IllegalArgumentException()
"FAIL"
}
}
private val checkedLazyModes =
if (Platform.memoryModel != MemoryModel.EXPERIMENTAL)
listOf(LazyThreadSafetyMode.PUBLICATION)
else
listOf(LazyThreadSafetyMode.SYNCHRONIZED, LazyThreadSafetyMode.PUBLICATION)
@Test fun runTest1() {
assertFailsWith<IllegalStateException> {
println(Lazy().recursion)
}
assertFailsWith<IllegalStateException> {
println(Lazy().freeze().recursion)
for (mode in checkedLazyModes) {
// We decided to synchonaize behaviour to be consistent with jvm version.
// Anyway, it's doesn't looks like well-defined case
if (Platform.memoryModel == MemoryModel.EXPERIMENTAL) {
val expected = if (mode == LazyThreadSafetyMode.SYNCHRONIZED) 46 else 42
y = 20
assertEquals(Lazy(mode).finiteRecursion, expected)
y = 20
assertEquals(Lazy(mode).freeze().finiteRecursion, expected)
} else {
assertFailsWith<IllegalStateException> {
println(Lazy(mode).recursion)
}
assertFailsWith<IllegalStateException> {
println(Lazy(mode).freeze().recursion)
}
y = 20
assertFailsWith<IllegalStateException> {
println(Lazy(mode).finiteRecursion)
}
y = 20
assertFailsWith<IllegalStateException> {
println(Lazy(mode).freeze().finiteRecursion)
}
}
}
}
@Test fun runTest2() {
var sum = 0
for (i in 1 .. 100) {
val self = Lazy().freeze()
assertEquals(self, self.self)
sum += self.self.hashCode()
for (mode in checkedLazyModes) {
var sum = 0
for (i in 1..100) {
val self = Lazy(mode).freeze()
assertEquals(self, self.self)
sum += self.self.hashCode()
}
}
println("OK")
}
@Test fun runTest3() {
assertFailsWith<InvalidMutabilityException> {
println(Lazy().freezer)
for (mode in checkedLazyModes) {
assertFailsWith<InvalidMutabilityException> {
println(Lazy(mode).freezer)
}
}
}
@Test fun runTest4() {
val self = Lazy()
repeat(10) {
assertFailsWith<IllegalArgumentException> {
println(self.thrower)
for (mode in checkedLazyModes) {
val self = Lazy(mode)
repeat(10) {
assertFailsWith<IllegalArgumentException> {
println(self.thrower)
}
}
}
}
@@ -11,16 +11,20 @@ import kotlin.native.concurrent.*
const val WORKERS_COUNT = 20
class C(private val initializer: () -> Int) {
val data by lazy { initializer() }
class IntHolder(val value:Int)
class C(mode: LazyThreadSafetyMode, private val initializer: () -> IntHolder) {
val data by lazy(mode) { initializer() }
}
fun concurrentLazyAccess(freeze: Boolean) {
fun concurrentLazyAccess(freeze: Boolean, mode: LazyThreadSafetyMode) {
// in old mm PUBLICATION is in fact SYNCHRONIZED, while SYNCHRONIZED is not supported
val argumentMode = if (Platform.memoryModel == MemoryModel.EXPERIMENTAL) mode else LazyThreadSafetyMode.PUBLICATION
val initializerCallCount = AtomicInt(0)
val c = C {
val c = C(argumentMode) {
initializerCallCount.increment()
42
IntHolder(42)
}
if (freeze) {
c.freeze()
@@ -40,14 +44,18 @@ fun concurrentLazyAccess(freeze: Boolean) {
while (inited.value < workers.size) {}
canStart.value = 1
futures.forEach {
assertEquals(42, it.result)
val results = futures.map { it.result }
results.forEach {
assertEquals(42, it.value)
assertSame(results[0], it)
}
workers.forEach {
it.requestTermination().result
}
assertEquals(1, initializerCallCount.value)
if (mode == LazyThreadSafetyMode.SYNCHRONIZED) {
assertEquals(1, initializerCallCount.value)
}
}
@Test
@@ -55,11 +63,13 @@ fun concurrentLazyAccessUnfrozen() {
if (Platform.memoryModel != MemoryModel.EXPERIMENTAL) {
return
}
concurrentLazyAccess(false)
concurrentLazyAccess(false, LazyThreadSafetyMode.SYNCHRONIZED)
concurrentLazyAccess(false, LazyThreadSafetyMode.PUBLICATION)
}
@Test
fun concurrentLazyAccessFrozen() {
concurrentLazyAccess(true)
concurrentLazyAccess(true, LazyThreadSafetyMode.SYNCHRONIZED)
concurrentLazyAccess(true, LazyThreadSafetyMode.PUBLICATION)
}
@@ -5,9 +5,10 @@
package kotlin
import kotlin.native.concurrent.FreezeAwareLazyImpl
import kotlin.native.concurrent.*
import kotlin.native.internal.FixmeConcurrency
import kotlin.reflect.KProperty
import kotlin.native.isExperimentalMM
/**
* Creates a new instance of the [Lazy] that uses the specified initialization function [initializer]
@@ -18,7 +19,13 @@ import kotlin.reflect.KProperty
* Note that the returned instance uses itself to synchronize on. Do not synchronize from external code on
* the returned instance as it may cause accidental deadlock. Also this behavior can be changed in the future.
*/
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = FreezeAwareLazyImpl(initializer)
@OptIn(kotlin.ExperimentalStdlibApi::class)
public actual fun <T> lazy(initializer: () -> T): Lazy<T> =
if (isExperimentalMM())
SynchronizedLazyImpl(initializer)
else
FreezeAwareLazyImpl(initializer)
/**
* Creates a new instance of the [Lazy] that uses the specified initialization function [initializer]
@@ -31,10 +38,11 @@ public actual fun <T> lazy(initializer: () -> T): Lazy<T> = FreezeAwareLazyImpl(
* Also this behavior can be changed in the future.
*/
@FixmeConcurrency
@OptIn(kotlin.ExperimentalStdlibApi::class)
public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
when (mode) {
LazyThreadSafetyMode.SYNCHRONIZED -> throw UnsupportedOperationException()
LazyThreadSafetyMode.PUBLICATION -> FreezeAwareLazyImpl(initializer)
LazyThreadSafetyMode.SYNCHRONIZED -> if (isExperimentalMM()) SynchronizedLazyImpl(initializer) else throw UnsupportedOperationException()
LazyThreadSafetyMode.PUBLICATION -> if (isExperimentalMM()) SafePublicationLazyImpl(initializer) else FreezeAwareLazyImpl(initializer)
LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
}
@@ -121,3 +121,78 @@ internal class AtomicLazyImpl<out T>(initializer: () -> T) : Lazy<T> {
* such as object signletons, or in cases where it's guaranteed not to have cyclical garbage.
*/
public fun <T> atomicLazy(initializer: () -> T): Lazy<T> = AtomicLazyImpl(initializer)
@Suppress("UNCHECKED_CAST")
internal class SynchronizedLazyImpl<out T>(initializer: () -> T) : Lazy<T> {
private var initializer = FreezableAtomicReference<(() -> T)?>(initializer)
private var valueRef = FreezableAtomicReference<Any?>(UNINITIALIZED)
private val lock = Lock()
override val value: T
get() {
val _v1 = valueRef.value
if (_v1 !== UNINITIALIZED) {
return _v1 as T
}
return locked(lock) {
val _v2 = valueRef.value
if (_v2 === UNINITIALIZED) {
val wasFrozen = this.isFrozen
val typedValue = initializer.value!!()
if (this.isFrozen) {
if (!wasFrozen) {
throw InvalidMutabilityException("Frozen during lazy computation")
}
typedValue.freeze()
}
valueRef.value = typedValue
initializer.value = null
typedValue
} else {
_v2 as T
}
}
}
override fun isInitialized() = valueRef.value !== UNINITIALIZED
override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
}
@Suppress("UNCHECKED_CAST")
internal class SafePublicationLazyImpl<out T>(initializer: () -> T) : Lazy<T> {
private var initializer = FreezableAtomicReference<(() -> T)?>(initializer)
private var valueRef = FreezableAtomicReference<Any?>(UNINITIALIZED)
override val value: T
get() {
val value = valueRef.value
if (value !== UNINITIALIZED) {
return value as T
}
val initializerValue = initializer.value
// if we see null in initializer here, it means that the value is already set by another thread
if (initializerValue != null) {
val wasFrozen = this.isFrozen
val newValue = initializerValue()
if (this.isFrozen) {
if (!wasFrozen) {
throw InvalidMutabilityException("Frozen during lazy computation")
}
newValue.freeze()
}
if (valueRef.compareAndSet(UNINITIALIZED, newValue)) {
initializer.value = null
return newValue
}
}
return valueRef.value as T
}
override fun isInitialized(): Boolean = valueRef.value !== UNINITIALIZED
override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
}