APIs for easier freeze debugging. (#1783)
This commit is contained in:
@@ -692,6 +692,12 @@ task freeze3(type: RunKonanTest) {
|
|||||||
source = "runtime/workers/freeze3.kt"
|
source = "runtime/workers/freeze3.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task freeze4(type: RunKonanTest) {
|
||||||
|
disabled = (project.testTarget == 'wasm32') // No exceptions on WASM.
|
||||||
|
goldValue = "OK\n"
|
||||||
|
source = "runtime/workers/freeze4.kt"
|
||||||
|
}
|
||||||
|
|
||||||
task atomic0(type: RunKonanTest) {
|
task atomic0(type: RunKonanTest) {
|
||||||
disabled = (project.testTarget == 'wasm32') // Workers need pthreads.
|
disabled = (project.testTarget == 'wasm32') // Workers need pthreads.
|
||||||
goldValue = "35\n" + "20\n" + "OK\n"
|
goldValue = "35\n" + "20\n" + "OK\n"
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package runtime.workers.freeze4
|
||||||
|
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
import konan.worker.*
|
||||||
|
|
||||||
|
data class Data(val x: Int, val s: String, val next: Data? = null)
|
||||||
|
|
||||||
|
@Test fun runTest() {
|
||||||
|
val data1 = Data(1, "")
|
||||||
|
data1.freeze()
|
||||||
|
assertFailsWith<FreezingException> {
|
||||||
|
data1.ensureNeverFrozen()
|
||||||
|
}
|
||||||
|
|
||||||
|
val dataNF = Data(42, "42")
|
||||||
|
dataNF.ensureNeverFrozen()
|
||||||
|
val data2 = Data(2, "2", dataNF)
|
||||||
|
assertFailsWith<FreezingException> {
|
||||||
|
data2.freeze()
|
||||||
|
}
|
||||||
|
assert(!data2.isFrozen)
|
||||||
|
println("OK")
|
||||||
|
}
|
||||||
@@ -223,7 +223,6 @@ constexpr const char* MemoryStatistic::indexToName[];
|
|||||||
#endif // COLLECT_STATISTIC
|
#endif // COLLECT_STATISTIC
|
||||||
|
|
||||||
struct MemoryState {
|
struct MemoryState {
|
||||||
|
|
||||||
#if TRACE_MEMORY
|
#if TRACE_MEMORY
|
||||||
// Set of all containers.
|
// Set of all containers.
|
||||||
ContainerHeaderSet* containers;
|
ContainerHeaderSet* containers;
|
||||||
@@ -419,7 +418,7 @@ RUNTIME_USED ContainerHeader theStaticObjectsContainer = {
|
|||||||
|
|
||||||
void objc_release(void* ptr);
|
void objc_release(void* ptr);
|
||||||
void Kotlin_ObjCExport_releaseAssociatedObject(void* associatedObject);
|
void Kotlin_ObjCExport_releaseAssociatedObject(void* associatedObject);
|
||||||
RUNTIME_NORETURN void ThrowFreezingException();
|
RUNTIME_NORETURN void ThrowFreezingException(KRef toFreeze, KRef blocker);
|
||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|
||||||
@@ -1536,11 +1535,18 @@ bool ClearSubgraphReferences(ObjHeader* root, bool checked) {
|
|||||||
* - not 'marked' and not 'seen' as WHITE marker (object is unprocessed)
|
* - not 'marked' and not 'seen' as WHITE marker (object is unprocessed)
|
||||||
* When we see GREY during DFS, it means we see cycle.
|
* When we see GREY during DFS, it means we see cycle.
|
||||||
*/
|
*/
|
||||||
void depthFirstTraversal(ContainerHeader* container, bool* hasCycles, KStdVector<ContainerHeader*>& order) {
|
void depthFirstTraversal(ContainerHeader* container, bool* hasCycles,
|
||||||
|
KRef* firstBlocker, KStdVector<ContainerHeader*>& order) {
|
||||||
// Mark GRAY.
|
// Mark GRAY.
|
||||||
container->setSeen();
|
container->setSeen();
|
||||||
|
|
||||||
traverseContainerReferredObjects(container, [hasCycles, &order](ObjHeader* obj) {
|
traverseContainerReferredObjects(container, [hasCycles, firstBlocker, &order](ObjHeader* obj) {
|
||||||
|
if (*firstBlocker != nullptr)
|
||||||
|
return;
|
||||||
|
if (obj->has_meta_object() && ((obj->meta_object()->flags_ & MF_NEVER_FROZEN) != 0)) {
|
||||||
|
*firstBlocker = obj;
|
||||||
|
return;
|
||||||
|
}
|
||||||
ContainerHeader* objContainer = obj->container();
|
ContainerHeader* objContainer = obj->container();
|
||||||
if (!objContainer->permanentOrFrozen()) {
|
if (!objContainer->permanentOrFrozen()) {
|
||||||
// Marked GREY, there's cycle.
|
// Marked GREY, there's cycle.
|
||||||
@@ -1548,7 +1554,7 @@ void depthFirstTraversal(ContainerHeader* container, bool* hasCycles, KStdVector
|
|||||||
|
|
||||||
// Go deeper if WHITE.
|
// Go deeper if WHITE.
|
||||||
if (!objContainer->seen() && !objContainer->marked()) {
|
if (!objContainer->seen() && !objContainer->marked()) {
|
||||||
depthFirstTraversal(objContainer, hasCycles, order);
|
depthFirstTraversal(objContainer, hasCycles, firstBlocker, order);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1687,8 +1693,12 @@ void FreezeSubgraph(ObjHeader* root) {
|
|||||||
|
|
||||||
// Do DFS cycle detection.
|
// Do DFS cycle detection.
|
||||||
bool hasCycles = false;
|
bool hasCycles = false;
|
||||||
|
KRef firstBlocker = nullptr;
|
||||||
KStdVector<ContainerHeader*> order;
|
KStdVector<ContainerHeader*> order;
|
||||||
depthFirstTraversal(rootContainer, &hasCycles, order);
|
depthFirstTraversal(rootContainer, &hasCycles, &firstBlocker, order);
|
||||||
|
if (firstBlocker != nullptr) {
|
||||||
|
ThrowFreezingException(root, firstBlocker);
|
||||||
|
}
|
||||||
// Now unmark all marked objects, and freeze them, if no cycles detected.
|
// Now unmark all marked objects, and freeze them, if no cycles detected.
|
||||||
if (hasCycles) {
|
if (hasCycles) {
|
||||||
freezeCyclic(rootContainer, order);
|
freezeCyclic(rootContainer, order);
|
||||||
@@ -1743,4 +1753,12 @@ OBJ_GETTER(ReadRefLocked, ObjHeader** location, int32_t* spinlock) {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EnsureNeverFrozen(KRef object) {
|
||||||
|
if (object->container()->frozen())
|
||||||
|
ThrowFreezingException(object, object);
|
||||||
|
// TODO: note, that this API could not not be called on frozen objects, so no need to care much about concurrency,
|
||||||
|
// although there's subtle race with case, where other thread freezes the same object after check.
|
||||||
|
object->meta_object()->flags_ |= MF_NEVER_FROZEN;
|
||||||
|
}
|
||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|||||||
@@ -246,6 +246,8 @@ struct MetaObjHeader {
|
|||||||
const TypeInfo* typeInfo_;
|
const TypeInfo* typeInfo_;
|
||||||
// Strong reference to counter object.
|
// Strong reference to counter object.
|
||||||
ObjHeader* counter_;
|
ObjHeader* counter_;
|
||||||
|
// Flags for object state.
|
||||||
|
int32_t flags_;
|
||||||
|
|
||||||
#ifdef KONAN_OBJC_INTEROP
|
#ifdef KONAN_OBJC_INTEROP
|
||||||
void* associatedObject_;
|
void* associatedObject_;
|
||||||
@@ -470,7 +472,9 @@ OBJ_GETTER(AdoptStablePointer, void*) RUNTIME_NOTHROW;
|
|||||||
// Check mutability state.
|
// Check mutability state.
|
||||||
void MutationCheck(ObjHeader* obj);
|
void MutationCheck(ObjHeader* obj);
|
||||||
// Freeze object subgraph.
|
// Freeze object subgraph.
|
||||||
void FreezeSubgraph(ObjHeader* root);
|
void FreezeSubgraph(ObjHeader* obj);
|
||||||
|
// Ensure this object shall block freezing.
|
||||||
|
void EnsureNeverFrozen(ObjHeader* obj);
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -61,6 +61,10 @@ enum Konan_TypeFlags {
|
|||||||
TF_IMMUTABLE = 1 << 0
|
TF_IMMUTABLE = 1 << 0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum Konan_MetaFlags {
|
||||||
|
MF_NEVER_FROZEN = 1 << 0
|
||||||
|
};
|
||||||
|
|
||||||
// Extended information about a type.
|
// Extended information about a type.
|
||||||
struct ExtendedTypeInfo {
|
struct ExtendedTypeInfo {
|
||||||
// Number of fields (negated Konan_RuntimeType for array types).
|
// Number of fields (negated Konan_RuntimeType for array types).
|
||||||
|
|||||||
@@ -577,4 +577,8 @@ KBoolean Kotlin_Worker_isFrozenInternal(KRef object) {
|
|||||||
return object == nullptr || object->container()->permanentOrFrozen();
|
return object == nullptr || object->container()->permanentOrFrozen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Kotlin_Worker_ensureNeverFrozen(KRef object) {
|
||||||
|
EnsureNeverFrozen(object);
|
||||||
|
}
|
||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ import konan.internal.ExportForCppRuntime
|
|||||||
/**
|
/**
|
||||||
* Exception thrown whenever freezing is not possible.
|
* Exception thrown whenever freezing is not possible.
|
||||||
*/
|
*/
|
||||||
public class FreezingException() : RuntimeException()
|
public class FreezingException(toFreeze: Any, blocker: Any) :
|
||||||
|
RuntimeException("freezing of $toFreeze has failed, first blocker is $blocker")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exception thrown whenever we attempt to mutate frozen objects.
|
* Exception thrown whenever we attempt to mutate frozen objects.
|
||||||
@@ -41,6 +42,14 @@ fun <T> T.freeze(): T {
|
|||||||
val Any?.isFrozen
|
val Any?.isFrozen
|
||||||
get() = isFrozenInternal(this)
|
get() = isFrozenInternal(this)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function ensures that if we see such an object during freezing attempt - freeze fails and FreezingException
|
||||||
|
* is thrown. Is object is already frozen - FreezingException is thrown immediately.
|
||||||
|
*/
|
||||||
|
@SymbolName("Kotlin_Worker_ensureNeverFrozen")
|
||||||
|
external fun Any.ensureNeverFrozen()
|
||||||
|
|
||||||
@SymbolName("Kotlin_Worker_freezeInternal")
|
@SymbolName("Kotlin_Worker_freezeInternal")
|
||||||
internal external fun freezeInternal(it: Any?)
|
internal external fun freezeInternal(it: Any?)
|
||||||
|
|
||||||
@@ -48,7 +57,8 @@ internal external fun freezeInternal(it: Any?)
|
|||||||
internal external fun isFrozenInternal(it: Any?): Boolean
|
internal external fun isFrozenInternal(it: Any?): Boolean
|
||||||
|
|
||||||
@ExportForCppRuntime
|
@ExportForCppRuntime
|
||||||
internal fun ThrowFreezingException(): Nothing = throw FreezingException()
|
internal fun ThrowFreezingException(toFreeze: Any, blocker: Any): Nothing =
|
||||||
|
throw FreezingException(toFreeze, blocker)
|
||||||
|
|
||||||
@ExportForCppRuntime
|
@ExportForCppRuntime
|
||||||
internal fun ThrowInvalidMutabilityException(where: Any): Nothing = throw InvalidMutabilityException(where)
|
internal fun ThrowInvalidMutabilityException(where: Any): Nothing = throw InvalidMutabilityException(where)
|
||||||
|
|||||||
Reference in New Issue
Block a user