[K/N] Implement Lazy for new mm
This commit is contained in:
@@ -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."
|
||||
}
|
||||
Reference in New Issue
Block a user