[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.test.*
|
||||||
import kotlin.native.concurrent.*
|
import kotlin.native.concurrent.*
|
||||||
|
|
||||||
class Lazy {
|
private var y = 20
|
||||||
|
|
||||||
|
class Lazy(mode: LazyThreadSafetyMode) {
|
||||||
val x = 17
|
val x = 17
|
||||||
val self by lazy { this }
|
val self by lazy(mode) { this }
|
||||||
val recursion: Int by lazy {
|
val recursion: Int by lazy(mode) {
|
||||||
if (x < 17) 42 else recursion
|
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()
|
freeze()
|
||||||
42
|
42
|
||||||
}
|
}
|
||||||
val thrower: String by lazy {
|
val thrower: String by lazy(mode) {
|
||||||
if (x < 100) throw IllegalArgumentException()
|
if (x < 100) throw IllegalArgumentException()
|
||||||
"FAIL"
|
"FAIL"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val checkedLazyModes =
|
||||||
|
if (Platform.memoryModel != MemoryModel.EXPERIMENTAL)
|
||||||
|
listOf(LazyThreadSafetyMode.PUBLICATION)
|
||||||
|
else
|
||||||
|
listOf(LazyThreadSafetyMode.SYNCHRONIZED, LazyThreadSafetyMode.PUBLICATION)
|
||||||
|
|
||||||
@Test fun runTest1() {
|
@Test fun runTest1() {
|
||||||
assertFailsWith<IllegalStateException> {
|
for (mode in checkedLazyModes) {
|
||||||
println(Lazy().recursion)
|
// We decided to synchonaize behaviour to be consistent with jvm version.
|
||||||
}
|
// Anyway, it's doesn't looks like well-defined case
|
||||||
assertFailsWith<IllegalStateException> {
|
if (Platform.memoryModel == MemoryModel.EXPERIMENTAL) {
|
||||||
println(Lazy().freeze().recursion)
|
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() {
|
@Test fun runTest2() {
|
||||||
var sum = 0
|
for (mode in checkedLazyModes) {
|
||||||
for (i in 1 .. 100) {
|
var sum = 0
|
||||||
val self = Lazy().freeze()
|
for (i in 1..100) {
|
||||||
assertEquals(self, self.self)
|
val self = Lazy(mode).freeze()
|
||||||
sum += self.self.hashCode()
|
assertEquals(self, self.self)
|
||||||
|
sum += self.self.hashCode()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
println("OK")
|
println("OK")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test fun runTest3() {
|
@Test fun runTest3() {
|
||||||
assertFailsWith<InvalidMutabilityException> {
|
for (mode in checkedLazyModes) {
|
||||||
println(Lazy().freezer)
|
assertFailsWith<InvalidMutabilityException> {
|
||||||
|
println(Lazy(mode).freezer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun runTest4() {
|
@Test fun runTest4() {
|
||||||
val self = Lazy()
|
for (mode in checkedLazyModes) {
|
||||||
repeat(10) {
|
val self = Lazy(mode)
|
||||||
assertFailsWith<IllegalArgumentException> {
|
repeat(10) {
|
||||||
println(self.thrower)
|
assertFailsWith<IllegalArgumentException> {
|
||||||
|
println(self.thrower)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,16 +11,20 @@ import kotlin.native.concurrent.*
|
|||||||
|
|
||||||
const val WORKERS_COUNT = 20
|
const val WORKERS_COUNT = 20
|
||||||
|
|
||||||
class C(private val initializer: () -> Int) {
|
class IntHolder(val value:Int)
|
||||||
val data by lazy { initializer() }
|
|
||||||
|
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 initializerCallCount = AtomicInt(0)
|
||||||
|
|
||||||
val c = C {
|
val c = C(argumentMode) {
|
||||||
initializerCallCount.increment()
|
initializerCallCount.increment()
|
||||||
42
|
IntHolder(42)
|
||||||
}
|
}
|
||||||
if (freeze) {
|
if (freeze) {
|
||||||
c.freeze()
|
c.freeze()
|
||||||
@@ -40,14 +44,18 @@ fun concurrentLazyAccess(freeze: Boolean) {
|
|||||||
while (inited.value < workers.size) {}
|
while (inited.value < workers.size) {}
|
||||||
canStart.value = 1
|
canStart.value = 1
|
||||||
|
|
||||||
futures.forEach {
|
val results = futures.map { it.result }
|
||||||
assertEquals(42, it.result)
|
results.forEach {
|
||||||
|
assertEquals(42, it.value)
|
||||||
|
assertSame(results[0], it)
|
||||||
}
|
}
|
||||||
workers.forEach {
|
workers.forEach {
|
||||||
it.requestTermination().result
|
it.requestTermination().result
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEquals(1, initializerCallCount.value)
|
if (mode == LazyThreadSafetyMode.SYNCHRONIZED) {
|
||||||
|
assertEquals(1, initializerCallCount.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -55,11 +63,13 @@ fun concurrentLazyAccessUnfrozen() {
|
|||||||
if (Platform.memoryModel != MemoryModel.EXPERIMENTAL) {
|
if (Platform.memoryModel != MemoryModel.EXPERIMENTAL) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
concurrentLazyAccess(false)
|
concurrentLazyAccess(false, LazyThreadSafetyMode.SYNCHRONIZED)
|
||||||
|
concurrentLazyAccess(false, LazyThreadSafetyMode.PUBLICATION)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun concurrentLazyAccessFrozen() {
|
fun concurrentLazyAccessFrozen() {
|
||||||
concurrentLazyAccess(true)
|
concurrentLazyAccess(true, LazyThreadSafetyMode.SYNCHRONIZED)
|
||||||
|
concurrentLazyAccess(true, LazyThreadSafetyMode.PUBLICATION)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,10 @@
|
|||||||
|
|
||||||
package kotlin
|
package kotlin
|
||||||
|
|
||||||
import kotlin.native.concurrent.FreezeAwareLazyImpl
|
import kotlin.native.concurrent.*
|
||||||
import kotlin.native.internal.FixmeConcurrency
|
import kotlin.native.internal.FixmeConcurrency
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
|
import kotlin.native.isExperimentalMM
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of the [Lazy] that uses the specified initialization function [initializer]
|
* 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
|
* 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.
|
* 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]
|
* 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.
|
* Also this behavior can be changed in the future.
|
||||||
*/
|
*/
|
||||||
@FixmeConcurrency
|
@FixmeConcurrency
|
||||||
|
@OptIn(kotlin.ExperimentalStdlibApi::class)
|
||||||
public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
|
public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
|
||||||
when (mode) {
|
when (mode) {
|
||||||
LazyThreadSafetyMode.SYNCHRONIZED -> throw UnsupportedOperationException()
|
LazyThreadSafetyMode.SYNCHRONIZED -> if (isExperimentalMM()) SynchronizedLazyImpl(initializer) else throw UnsupportedOperationException()
|
||||||
LazyThreadSafetyMode.PUBLICATION -> FreezeAwareLazyImpl(initializer)
|
LazyThreadSafetyMode.PUBLICATION -> if (isExperimentalMM()) SafePublicationLazyImpl(initializer) else FreezeAwareLazyImpl(initializer)
|
||||||
LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(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.
|
* 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)
|
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