APIs for easier freeze debugging. (#1783)
This commit is contained in:
@@ -692,6 +692,12 @@ task freeze3(type: RunKonanTest) {
|
||||
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) {
|
||||
disabled = (project.testTarget == 'wasm32') // Workers need pthreads.
|
||||
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
|
||||
|
||||
struct MemoryState {
|
||||
|
||||
#if TRACE_MEMORY
|
||||
// Set of all containers.
|
||||
ContainerHeaderSet* containers;
|
||||
@@ -419,7 +418,7 @@ RUNTIME_USED ContainerHeader theStaticObjectsContainer = {
|
||||
|
||||
void objc_release(void* ptr);
|
||||
void Kotlin_ObjCExport_releaseAssociatedObject(void* associatedObject);
|
||||
RUNTIME_NORETURN void ThrowFreezingException();
|
||||
RUNTIME_NORETURN void ThrowFreezingException(KRef toFreeze, KRef blocker);
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@@ -1536,11 +1535,18 @@ bool ClearSubgraphReferences(ObjHeader* root, bool checked) {
|
||||
* - not 'marked' and not 'seen' as WHITE marker (object is unprocessed)
|
||||
* 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.
|
||||
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();
|
||||
if (!objContainer->permanentOrFrozen()) {
|
||||
// Marked GREY, there's cycle.
|
||||
@@ -1548,7 +1554,7 @@ void depthFirstTraversal(ContainerHeader* container, bool* hasCycles, KStdVector
|
||||
|
||||
// Go deeper if WHITE.
|
||||
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.
|
||||
bool hasCycles = false;
|
||||
KRef firstBlocker = nullptr;
|
||||
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.
|
||||
if (hasCycles) {
|
||||
freezeCyclic(rootContainer, order);
|
||||
@@ -1743,4 +1753,12 @@ OBJ_GETTER(ReadRefLocked, ObjHeader** location, int32_t* spinlock) {
|
||||
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"
|
||||
|
||||
@@ -246,6 +246,8 @@ struct MetaObjHeader {
|
||||
const TypeInfo* typeInfo_;
|
||||
// Strong reference to counter object.
|
||||
ObjHeader* counter_;
|
||||
// Flags for object state.
|
||||
int32_t flags_;
|
||||
|
||||
#ifdef KONAN_OBJC_INTEROP
|
||||
void* associatedObject_;
|
||||
@@ -470,7 +472,9 @@ OBJ_GETTER(AdoptStablePointer, void*) RUNTIME_NOTHROW;
|
||||
// Check mutability state.
|
||||
void MutationCheck(ObjHeader* obj);
|
||||
// Freeze object subgraph.
|
||||
void FreezeSubgraph(ObjHeader* root);
|
||||
void FreezeSubgraph(ObjHeader* obj);
|
||||
// Ensure this object shall block freezing.
|
||||
void EnsureNeverFrozen(ObjHeader* obj);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -61,6 +61,10 @@ enum Konan_TypeFlags {
|
||||
TF_IMMUTABLE = 1 << 0
|
||||
};
|
||||
|
||||
enum Konan_MetaFlags {
|
||||
MF_NEVER_FROZEN = 1 << 0
|
||||
};
|
||||
|
||||
// Extended information about a type.
|
||||
struct ExtendedTypeInfo {
|
||||
// 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();
|
||||
}
|
||||
|
||||
void Kotlin_Worker_ensureNeverFrozen(KRef object) {
|
||||
EnsureNeverFrozen(object);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@@ -21,7 +21,8 @@ import konan.internal.ExportForCppRuntime
|
||||
/**
|
||||
* 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.
|
||||
@@ -41,6 +42,14 @@ fun <T> T.freeze(): T {
|
||||
val Any?.isFrozen
|
||||
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")
|
||||
internal external fun freezeInternal(it: Any?)
|
||||
|
||||
@@ -48,7 +57,8 @@ internal external fun freezeInternal(it: Any?)
|
||||
internal external fun isFrozenInternal(it: Any?): Boolean
|
||||
|
||||
@ExportForCppRuntime
|
||||
internal fun ThrowFreezingException(): Nothing = throw FreezingException()
|
||||
internal fun ThrowFreezingException(toFreeze: Any, blocker: Any): Nothing =
|
||||
throw FreezingException(toFreeze, blocker)
|
||||
|
||||
@ExportForCppRuntime
|
||||
internal fun ThrowInvalidMutabilityException(where: Any): Nothing = throw InvalidMutabilityException(where)
|
||||
|
||||
Reference in New Issue
Block a user