Rework lazy due to cyclic collector. (#3861)
This commit is contained in:
@@ -6,17 +6,55 @@
|
||||
package runtime.workers.lazy1
|
||||
|
||||
import kotlin.test.*
|
||||
|
||||
import kotlin.native.concurrent.*
|
||||
|
||||
class Leak {
|
||||
val leak by lazy { this }
|
||||
class Lazy {
|
||||
val x = 17
|
||||
val self by lazy { this }
|
||||
val recursion: Int by lazy {
|
||||
if (x < 17) 42 else recursion
|
||||
}
|
||||
val freezer: Int by lazy {
|
||||
freeze()
|
||||
42
|
||||
}
|
||||
val thrower: String by lazy {
|
||||
if (x < 100) throw IllegalArgumentException()
|
||||
"FAIL"
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun runTest() {
|
||||
assertFailsWith<InvalidMutabilityException> {
|
||||
for (i in 1..100)
|
||||
Leak().freeze().leak
|
||||
@Test fun runTest1() {
|
||||
assertFailsWith<IllegalStateException> {
|
||||
println(Lazy().recursion)
|
||||
}
|
||||
assertFailsWith<IllegalStateException> {
|
||||
println(Lazy().freeze().recursion)
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun runTest2() {
|
||||
var sum = 0
|
||||
for (i in 1 .. 100) {
|
||||
val self = Lazy().freeze()
|
||||
assertEquals(self, self.self)
|
||||
sum += self.self.hashCode()
|
||||
}
|
||||
println("OK")
|
||||
}
|
||||
|
||||
|
||||
@Test fun runTest3() {
|
||||
assertFailsWith<InvalidMutabilityException> {
|
||||
println(Lazy().freezer)
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun runTest4() {
|
||||
val self = Lazy()
|
||||
repeat(10) {
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
println(self.thrower)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2545,49 +2545,6 @@ void ensureNeverFrozen(ObjHeader* object) {
|
||||
object->meta_object()->flags_ |= MF_NEVER_FROZEN;
|
||||
}
|
||||
|
||||
// TODO: incorrect, use 3-color scheme.
|
||||
KBoolean ensureAcyclicAndSet(ObjHeader* where, KInt index, ObjHeader* what) {
|
||||
RuntimeAssert(where->container() != nullptr && where->container()->frozen(), "Must be used on frozen objects only");
|
||||
RuntimeAssert(what == nullptr || isPermanentOrFrozen(what),
|
||||
"Must be used with an immutable value");
|
||||
if (what != nullptr) {
|
||||
// Now we check that `where` is not reachable from `what`.
|
||||
// As we cannot modify objects while traversing, instead we remember all seen objects in a set.
|
||||
KStdUnorderedSet<ContainerHeader*> seen;
|
||||
KStdDeque<ContainerHeader*> queue;
|
||||
if (what->container() != nullptr)
|
||||
queue.push_back(what->container());
|
||||
bool acyclic = true;
|
||||
while (!queue.empty() && acyclic) {
|
||||
ContainerHeader* current = queue.front();
|
||||
queue.pop_front();
|
||||
seen.insert(current);
|
||||
if (isAggregatingFrozenContainer(current)) {
|
||||
ContainerHeader** subContainer = reinterpret_cast<ContainerHeader**>(current + 1);
|
||||
for (int i = 0; i < current->objectCount(); ++i) {
|
||||
if (seen.count(*subContainer) == 0)
|
||||
queue.push_back(*subContainer++);
|
||||
}
|
||||
} else {
|
||||
traverseContainerReferredObjects(current, [where, &queue, &acyclic, &seen](ObjHeader* obj) {
|
||||
if (obj == where) {
|
||||
acyclic = false;
|
||||
} else {
|
||||
auto* objContainer = obj->container();
|
||||
if (objContainer != nullptr && seen.count(objContainer) == 0)
|
||||
queue.push_back(objContainer);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!acyclic) return false;
|
||||
}
|
||||
UpdateHeapRef(reinterpret_cast<ObjHeader**>(
|
||||
reinterpret_cast<uintptr_t>(where) + where->type_info()->objOffsets_[index]), what);
|
||||
// Fence on updated location?
|
||||
return true;
|
||||
}
|
||||
|
||||
void shareAny(ObjHeader* obj) {
|
||||
auto* container = obj->container();
|
||||
if (isShareable(container)) return;
|
||||
@@ -3040,10 +2997,6 @@ void EnsureNeverFrozen(ObjHeader* object) {
|
||||
ensureNeverFrozen(object);
|
||||
}
|
||||
|
||||
KBoolean Konan_ensureAcyclicAndSet(ObjHeader* where, KInt index, ObjHeader* what) {
|
||||
return ensureAcyclicAndSet(where, index, what);
|
||||
}
|
||||
|
||||
void Kotlin_Any_share(ObjHeader* obj) {
|
||||
shareAny(obj);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ public actual fun <T> lazy(initializer: () -> T): Lazy<T> = FreezeAwareLazyImpl(
|
||||
public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
|
||||
when (mode) {
|
||||
LazyThreadSafetyMode.SYNCHRONIZED -> throw UnsupportedOperationException()
|
||||
LazyThreadSafetyMode.PUBLICATION -> throw UnsupportedOperationException()
|
||||
LazyThreadSafetyMode.PUBLICATION -> FreezeAwareLazyImpl(initializer)
|
||||
LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,67 +6,61 @@
|
||||
package kotlin.native.concurrent
|
||||
|
||||
import kotlin.native.internal.Frozen
|
||||
import kotlin.native.internal.NoReorderFields
|
||||
|
||||
@SymbolName("Konan_ensureAcyclicAndSet")
|
||||
private external fun ensureAcyclicAndSet(where: Any, index: Int, what: Any?): Boolean
|
||||
|
||||
@SymbolName("ReadHeapRefNoLock")
|
||||
internal external fun readHeapRefNoLock(where: Any, index: Int): Any?
|
||||
|
||||
@NoReorderFields
|
||||
internal class FreezeAwareLazyImpl<out T>(initializer: () -> T) : Lazy<T> {
|
||||
// IMPORTANT: due to simplified ensureAcyclicAndSet() semantics fields here must be ordered like this,
|
||||
// as an ordinal is used to refer a field.
|
||||
private var value_: Any? = UNINITIALIZED
|
||||
private var initializer_: (() -> T)? = initializer
|
||||
private val value_ = FreezableAtomicReference<Any?>(UNINITIALIZED)
|
||||
private val initializer_ = FreezableAtomicReference<(() -> T)?>(initializer)
|
||||
private val lock_ = Lock()
|
||||
|
||||
private fun getOrInit(doFreeze: Boolean): T {
|
||||
var result = value_.value
|
||||
if (result !== UNINITIALIZED) {
|
||||
if (result === INITIALIZING) {
|
||||
value_.value = UNINITIALIZED
|
||||
throw IllegalStateException("Recursive lazy computation")
|
||||
}
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return result as T
|
||||
}
|
||||
// Set value_ to INITIALIZING.
|
||||
value_.value = INITIALIZING
|
||||
try {
|
||||
result = initializer_.value!!()
|
||||
if (doFreeze) result.freeze()
|
||||
} catch (throwable: Throwable) {
|
||||
value_.value = UNINITIALIZED
|
||||
throw throwable
|
||||
}
|
||||
if (!doFreeze && this.isFrozen) {
|
||||
value_.value = UNINITIALIZED
|
||||
throw InvalidMutabilityException("Frozen during lazy computation")
|
||||
}
|
||||
// Set value_ to actual one.
|
||||
value_.value = result
|
||||
// Clear initializer.
|
||||
initializer_.value = null
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return result as T
|
||||
}
|
||||
|
||||
override val value: T
|
||||
get() {
|
||||
if (isFrozen) {
|
||||
return if (isFrozen) {
|
||||
locked(lock_) {
|
||||
// Lock is already taken above.
|
||||
var result = readHeapRefNoLock(this, 0)
|
||||
if (result !== UNINITIALIZED) {
|
||||
assert(result !== INITIALIZING)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return result as T
|
||||
}
|
||||
// Set value_ to INITIALIZING.
|
||||
ensureAcyclicAndSet(this, 0, INITIALIZING)
|
||||
result = initializer_!!().freeze()
|
||||
// Set value_.
|
||||
if (!ensureAcyclicAndSet(this, 0, result)) {
|
||||
throw InvalidMutabilityException("Setting cyclic data via lazy in $this: $result")
|
||||
}
|
||||
// Do not clear initializer_ reference, as it may break freezing invariants and zero out
|
||||
// still valid object. It seems to be safe only in case when `this` is not reachable from
|
||||
// initializer.
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return result as T
|
||||
getOrInit(true)
|
||||
}
|
||||
} else {
|
||||
var result: Any? = value_
|
||||
if (result === UNINITIALIZED) {
|
||||
result = initializer_!!()
|
||||
if (isFrozen)
|
||||
throw InvalidMutabilityException("$this got frozen during lazy evaluation" )
|
||||
value_ = result
|
||||
initializer_ = null
|
||||
}
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return result as T
|
||||
getOrInit(false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This operation on shared objects may return value which is no longer reflect the current state of lazy.
|
||||
*/
|
||||
override fun isInitialized(): Boolean = (value_ !== UNINITIALIZED) && (value_ !== INITIALIZING)
|
||||
override fun isInitialized(): Boolean = (value_.value !== UNINITIALIZED) && (value_.value !== INITIALIZING)
|
||||
|
||||
override fun toString(): String = if (isInitialized())
|
||||
value.toString() else "Lazy value not initialized yet."
|
||||
value.toString() else "Lazy value not initialized yet"
|
||||
}
|
||||
|
||||
internal object UNINITIALIZED {
|
||||
|
||||
@@ -14,6 +14,9 @@ external private fun Any.share()
|
||||
@SymbolName("Kotlin_CPointer_CopyMemory")
|
||||
external private fun CopyMemory(to: COpaquePointer?, from: COpaquePointer?, count: Int)
|
||||
|
||||
@SymbolName("ReadHeapRefNoLock")
|
||||
internal external fun readHeapRefNoLock(where: Any, index: Int): Any?
|
||||
|
||||
/**
|
||||
* Mutable concurrently accessible data buffer. Could be accessed from several workers simulteniously.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user