APIs for easier freeze debugging. (#1783)

This commit is contained in:
Nikolay Igotti
2018-07-10 14:28:56 +03:00
committed by GitHub
parent 7e517ef6f1
commit da7e31a10a
7 changed files with 79 additions and 9 deletions
+6
View File
@@ -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")
}
+24 -6
View File
@@ -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"
+5 -1
View File
@@ -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
+4
View File
@@ -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).
+4
View File
@@ -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)