Shared cyclic garbage detector. (#3616)
This commit is contained in:
+1
@@ -25,6 +25,7 @@ object KonanFqNames {
|
||||
val threadLocal = FqName("kotlin.native.concurrent.ThreadLocal")
|
||||
val sharedImmutable = FqName("kotlin.native.concurrent.SharedImmutable")
|
||||
val frozen = FqName("kotlin.native.internal.Frozen")
|
||||
val leakDetectorCandidate = FqName("kotlin.native.internal.LeakDetectorCandidate")
|
||||
val typedIntrinsic = FqName("kotlin.native.internal.TypedIntrinsic")
|
||||
val objCMethod = FqName("kotlinx.cinterop.ObjCMethod")
|
||||
}
|
||||
|
||||
+6
@@ -60,6 +60,9 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
||||
result = result or TF_ACYCLIC
|
||||
}
|
||||
}
|
||||
if (irClass.hasAnnotation(KonanFqNames.leakDetectorCandidate)) {
|
||||
result = result or TF_LEAK_DETECTOR_CANDIDATE
|
||||
}
|
||||
if (irClass.isInterface)
|
||||
result = result or TF_INTERFACE
|
||||
return result
|
||||
@@ -569,3 +572,6 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
||||
private const val TF_IMMUTABLE = 1
|
||||
private const val TF_ACYCLIC = 2
|
||||
private const val TF_INTERFACE = 4
|
||||
private const val TF_OBJC_DYNAMIC = 8
|
||||
private const val TF_LEAK_DETECTOR_CANDIDATE = 16
|
||||
|
||||
|
||||
@@ -2721,6 +2721,12 @@ standaloneTest("memory_only_gc") {
|
||||
source = "runtime/memory/only_gc.kt"
|
||||
}
|
||||
|
||||
standaloneTest("leak_detector") {
|
||||
disabled = project.globalTestArgs.contains('-opt')
|
||||
flags = ['-g']
|
||||
source = "runtime/memory/leak_detector.kt"
|
||||
}
|
||||
|
||||
standaloneTest("mpp1") {
|
||||
source = "codegen/mpp/mpp1.kt"
|
||||
flags = ['-tr', '-Xmulti-platform']
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import kotlin.native.concurrent.*
|
||||
import kotlin.native.internal.GC
|
||||
import kotlin.test.*
|
||||
|
||||
/*
|
||||
* Typical snippet for the leak detector usage.
|
||||
*/
|
||||
fun dumpLeaks() {
|
||||
GC.collect()
|
||||
GC.detectCycles()?.let { cycles ->
|
||||
cycles.firstOrNull()?.let { root ->
|
||||
val cycle = GC.findCycle(root)
|
||||
println(cycle?.contentToString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun test1() {
|
||||
val a = AtomicReference<Any?>(null)
|
||||
a.value = a
|
||||
val cycles = GC.detectCycles()!!
|
||||
assertEquals(1, cycles.size)
|
||||
assertTrue(arrayOf(a).contentEquals(GC.findCycle(cycles[0])!!))
|
||||
a.value = null
|
||||
}
|
||||
|
||||
class Holder(var other: Any?)
|
||||
|
||||
fun test2() {
|
||||
val array = arrayOf(AtomicReference<Any?>(null), AtomicReference<Any?>(null))
|
||||
val obj1 = Holder(array).freeze()
|
||||
array[0].value = obj1
|
||||
val cycles = GC.detectCycles()!!
|
||||
assertEquals(1, cycles.size)
|
||||
assertTrue(arrayOf(obj1, array, array[0]).contentEquals(GC.findCycle(cycles[0])!!))
|
||||
array[0].value = null
|
||||
}
|
||||
|
||||
fun test3() {
|
||||
val a1 = FreezableAtomicReference<Any?>(null)
|
||||
val head = Holder(null)
|
||||
var current = head
|
||||
repeat(30) {
|
||||
val next = Holder(null)
|
||||
current.other = next
|
||||
current = next
|
||||
}
|
||||
a1.value = head
|
||||
current.other = a1
|
||||
val cycles = GC.detectCycles()!!
|
||||
assertEquals(1, cycles.size)
|
||||
val cycle = GC.findCycle(cycles[0])!!
|
||||
assertEquals(32, cycle.size)
|
||||
a1.value = null
|
||||
}
|
||||
|
||||
fun main() {
|
||||
test1()
|
||||
test2()
|
||||
test3()
|
||||
}
|
||||
+177
-24
@@ -89,8 +89,13 @@ constexpr size_t kMaxGcAllocThreshold = 8 * 1024 * 1024;
|
||||
|
||||
typedef KStdUnorderedSet<ContainerHeader*> ContainerHeaderSet;
|
||||
typedef KStdVector<ContainerHeader*> ContainerHeaderList;
|
||||
typedef KStdVector<KRef*> KRefPtrList;
|
||||
typedef KStdDeque<ContainerHeader*> ContainerHeaderDeque;
|
||||
typedef KStdVector<KRef> KRefList;
|
||||
typedef KStdVector<KRef*> KRefPtrList;
|
||||
typedef KStdUnorderedSet<KRef> KRefSet;
|
||||
typedef KStdUnorderedMap<KRef, KInt> KRefIntMap;
|
||||
typedef KStdDeque<KRef> KRefDeque;
|
||||
typedef KStdDeque<KRefList> KRefListDeque;
|
||||
|
||||
// A little hack that allows to enable -O2 optimizations
|
||||
// Prevents clang from replacing FrameOverlay struct
|
||||
@@ -104,6 +109,10 @@ volatile int aliveMemoryStatesCount = 0;
|
||||
|
||||
KBoolean g_checkLeaks = KonanNeedDebugInfo;
|
||||
|
||||
// Only used by the leak detector.
|
||||
KRef g_leakCheckerGlobalList = nullptr;
|
||||
KInt g_leakCheckerGlobalLock = 0;
|
||||
|
||||
// TODO: can we pass this variable as an explicit argument?
|
||||
THREAD_LOCAL_VARIABLE MemoryState* memoryState = nullptr;
|
||||
THREAD_LOCAL_VARIABLE FrameOverlay* currentFrame = nullptr;
|
||||
@@ -689,24 +698,38 @@ inline container_size_t objectSize(const ObjHeader* obj) {
|
||||
return alignUp(size, kObjectAlignment);
|
||||
}
|
||||
|
||||
template <typename func>
|
||||
inline void traverseObjectFields(ObjHeader* obj, func process) {
|
||||
const TypeInfo* typeInfo = obj->type_info();
|
||||
if (typeInfo != theArrayTypeInfo) {
|
||||
for (int index = 0; index < typeInfo->objOffsetsCount_; index++) {
|
||||
ObjHeader** location = reinterpret_cast<ObjHeader**>(
|
||||
reinterpret_cast<uintptr_t>(obj) + typeInfo->objOffsets_[index]);
|
||||
process(location);
|
||||
}
|
||||
} else {
|
||||
ArrayHeader* array = obj->array();
|
||||
for (int index = 0; index < array->count_; index++) {
|
||||
process(ArrayAddressOfElementAt(array, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename func>
|
||||
inline void traverseReferredObjects(ObjHeader* obj, func process) {
|
||||
traverseObjectFields(obj, [process](ObjHeader** location) {
|
||||
ObjHeader* ref = *location;
|
||||
if (ref != nullptr) process(ref);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename func>
|
||||
inline void traverseContainerObjectFields(ContainerHeader* container, func process) {
|
||||
RuntimeAssert(!isAggregatingFrozenContainer(container), "Must not be called on such containers");
|
||||
ObjHeader* obj = reinterpret_cast<ObjHeader*>(container + 1);
|
||||
|
||||
for (int object = 0; object < container->objectCount(); object++) {
|
||||
const TypeInfo* typeInfo = obj->type_info();
|
||||
if (typeInfo != theArrayTypeInfo) {
|
||||
for (int index = 0; index < typeInfo->objOffsetsCount_; index++) {
|
||||
ObjHeader** location = reinterpret_cast<ObjHeader**>(
|
||||
reinterpret_cast<uintptr_t>(obj) + typeInfo->objOffsets_[index]);
|
||||
process(location);
|
||||
}
|
||||
} else {
|
||||
ArrayHeader* array = obj->array();
|
||||
for (int index = 0; index < array->count_; index++) {
|
||||
process(ArrayAddressOfElementAt(array, index));
|
||||
}
|
||||
}
|
||||
traverseObjectFields(obj, process);
|
||||
obj = reinterpret_cast<ObjHeader*>(
|
||||
reinterpret_cast<uintptr_t>(obj) + objectSize(obj));
|
||||
}
|
||||
@@ -897,14 +920,29 @@ void freeAggregatingFrozenContainer(ContainerHeader* container) {
|
||||
|
||||
void runDeallocationHooks(ContainerHeader* container) {
|
||||
ObjHeader* obj = reinterpret_cast<ObjHeader*>(container + 1);
|
||||
|
||||
for (int index = 0; index < container->objectCount(); index++) {
|
||||
if (obj->has_meta_object()) {
|
||||
if (KonanNeedDebugInfo && (obj->type_info()->flags_ & TF_LEAK_DETECTOR_CANDIDATE) != 0 && g_checkLeaks) {
|
||||
// Remove the object from the double-linked list of potentially cyclic objects.
|
||||
auto* meta = obj->meta_object();
|
||||
lock(&g_leakCheckerGlobalLock);
|
||||
// Get previous.
|
||||
auto* previous = meta->LeakDetector.previous_;
|
||||
auto* previousMeta = (previous != nullptr) ? previous->meta_object() : nullptr;
|
||||
auto* next = meta->LeakDetector.next_;
|
||||
auto* nextMeta = (next != nullptr) ? next->meta_object() : nullptr;
|
||||
// Remove current.
|
||||
if (previous != nullptr)
|
||||
previous->meta_object()->LeakDetector.next_ = next;
|
||||
if (next != nullptr)
|
||||
next->meta_object()->LeakDetector.previous_ = previous;
|
||||
if (obj == g_leakCheckerGlobalList)
|
||||
g_leakCheckerGlobalList = next;
|
||||
unlock(&g_leakCheckerGlobalLock);
|
||||
}
|
||||
ObjHeader::destroyMetaObject(&obj->typeInfoOrMeta_);
|
||||
}
|
||||
|
||||
obj = reinterpret_cast<ObjHeader*>(
|
||||
reinterpret_cast<uintptr_t>(obj) + objectSize(obj));
|
||||
obj = reinterpret_cast<ObjHeader*>(reinterpret_cast<uintptr_t>(obj) + objectSize(obj));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1364,8 +1402,8 @@ void collectWhite(MemoryState* state, ContainerHeader* start) {
|
||||
toVisit.push_front(childContainer);
|
||||
}
|
||||
});
|
||||
runDeallocationHooks(container);
|
||||
scheduleDestroyContainer(state, container);
|
||||
runDeallocationHooks(container);
|
||||
scheduleDestroyContainer(state, container);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1877,6 +1915,18 @@ OBJ_GETTER(allocInstance, const TypeInfo* type_info) {
|
||||
checkIfGcNeeded(state);
|
||||
#endif // USE_GC
|
||||
auto container = ObjectContainer(state, type_info);
|
||||
ObjHeader* obj = container.GetPlace();
|
||||
if (KonanNeedDebugInfo && g_checkLeaks && (type_info->flags_ & TF_LEAK_DETECTOR_CANDIDATE) != 0) {
|
||||
// Add newly allocated object to the double-linked list of potentially cyclic objects.
|
||||
MetaObjHeader* meta = obj->meta_object();
|
||||
lock(&g_leakCheckerGlobalLock);
|
||||
KRef old = g_leakCheckerGlobalList;
|
||||
g_leakCheckerGlobalList = obj;
|
||||
meta->LeakDetector.next_ = old;
|
||||
if (old != nullptr)
|
||||
old->meta_object()->LeakDetector.previous_ = obj;
|
||||
unlock(&g_leakCheckerGlobalLock);
|
||||
}
|
||||
#if USE_GC
|
||||
if (Strict) {
|
||||
rememberNewContainer(container.header());
|
||||
@@ -1884,7 +1934,7 @@ OBJ_GETTER(allocInstance, const TypeInfo* type_info) {
|
||||
makeShareable(container.header());
|
||||
}
|
||||
#endif // USE_GC
|
||||
RETURN_OBJ(container.GetPlace());
|
||||
RETURN_OBJ(obj);
|
||||
}
|
||||
|
||||
template <bool Strict>
|
||||
@@ -2455,6 +2505,7 @@ 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),
|
||||
@@ -2504,6 +2555,99 @@ void shareAny(ObjHeader* obj) {
|
||||
container->makeShared();
|
||||
}
|
||||
|
||||
OBJ_GETTER0(detectCyclicReferences) {
|
||||
// Collect rootset, hold references to simplify remaining code.
|
||||
KRefList rootset;
|
||||
lock(&g_leakCheckerGlobalLock);
|
||||
auto* candidate = g_leakCheckerGlobalList;
|
||||
while (candidate != nullptr) {
|
||||
addHeapRef(candidate);
|
||||
rootset.push_back(candidate);
|
||||
candidate = candidate->meta_object()->LeakDetector.next_;
|
||||
}
|
||||
unlock(&g_leakCheckerGlobalLock);
|
||||
KRefSet cyclic;
|
||||
KRefSet seen;
|
||||
KRefDeque toVisit;
|
||||
for (auto* root: rootset) {
|
||||
seen.clear();
|
||||
toVisit.clear();
|
||||
traverseReferredObjects(root, [&toVisit](ObjHeader* obj) { toVisit.push_front(obj); });
|
||||
bool seenToRoot = false;
|
||||
while (!toVisit.empty() && !seenToRoot) {
|
||||
KRef current = toVisit.front();
|
||||
toVisit.pop_front();
|
||||
if (current == root) seenToRoot = true;
|
||||
// TODO: racy against concurrent mutators.
|
||||
if (seen.count(current) == 0) {
|
||||
traverseReferredObjects(current, [&toVisit](ObjHeader* obj) {
|
||||
toVisit.push_front(obj);
|
||||
});
|
||||
seen.insert(current);
|
||||
}
|
||||
}
|
||||
if (seenToRoot) {
|
||||
cyclic.insert(root);
|
||||
}
|
||||
}
|
||||
int numElements = cyclic.size();
|
||||
ArrayHeader* result = AllocArrayInstance(theArrayTypeInfo, numElements, OBJ_RESULT)->array();
|
||||
KRef* place = ArrayAddressOfElementAt(result, 0);
|
||||
for (auto* it: cyclic) {
|
||||
UpdateHeapRef(place++, it);
|
||||
}
|
||||
for (auto* root: rootset) {
|
||||
ReleaseHeapRef(root);
|
||||
}
|
||||
RETURN_OBJ(result->obj());
|
||||
}
|
||||
|
||||
OBJ_GETTER(findCycle, KRef root) {
|
||||
KRefSet seen;
|
||||
KRefListDeque queue;
|
||||
KRefDeque toVisit;
|
||||
KRefList path;
|
||||
traverseReferredObjects(root, [&toVisit](ObjHeader* obj) { toVisit.push_front(obj); });
|
||||
bool isFound = false;
|
||||
while (!toVisit.empty() && !isFound) {
|
||||
KRef current = toVisit.front();
|
||||
toVisit.pop_front();
|
||||
// Do DFS path search.
|
||||
KRefList first;
|
||||
first.push_back(current);
|
||||
queue.emplace_back(first);
|
||||
seen.clear();
|
||||
while (!queue.empty()) {
|
||||
KRefList currentPath = queue.back();
|
||||
queue.pop_back();
|
||||
KRef node = currentPath[currentPath.size() - 1];
|
||||
if (node == root) {
|
||||
isFound = true;
|
||||
path = currentPath;
|
||||
break;
|
||||
}
|
||||
if (seen.count(node) == 0) {
|
||||
// TODO: racy against concurrent mutators.
|
||||
traverseReferredObjects(node, [&queue, ¤tPath](ObjHeader* obj) {
|
||||
KRefList newPath(currentPath);
|
||||
newPath.push_back(obj);
|
||||
queue.emplace_back(newPath);
|
||||
});
|
||||
seen.insert(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
ArrayHeader* result = nullptr;
|
||||
if (isFound) {
|
||||
result = AllocArrayInstance(theArrayTypeInfo, path.size(), OBJ_RESULT)->array();
|
||||
KRef* place = ArrayAddressOfElementAt(result, 0);
|
||||
for (auto* it: path) {
|
||||
UpdateHeapRef(place++, it);
|
||||
}
|
||||
}
|
||||
RETURN_OBJ(result->obj());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MetaObjHeader* ObjHeader::createMetaObject(TypeInfo** location) {
|
||||
@@ -2535,9 +2679,9 @@ MetaObjHeader* ObjHeader::createMetaObject(TypeInfo** location) {
|
||||
void ObjHeader::destroyMetaObject(TypeInfo** location) {
|
||||
MetaObjHeader* meta = clearPointerBits(*(reinterpret_cast<MetaObjHeader**>(location)), OBJECT_TAG_MASK);
|
||||
*const_cast<const TypeInfo**>(location) = meta->typeInfo_;
|
||||
if (meta->counter_ != nullptr) {
|
||||
WeakReferenceCounterClear(meta->counter_);
|
||||
ZeroHeapRef(&meta->counter_);
|
||||
if (meta->WeakReference.counter_ != nullptr) {
|
||||
WeakReferenceCounterClear(meta->WeakReference.counter_);
|
||||
ZeroHeapRef(&meta->WeakReference.counter_);
|
||||
}
|
||||
|
||||
#ifdef KONAN_OBJC_INTEROP
|
||||
@@ -2906,6 +3050,15 @@ KBoolean Kotlin_native_internal_GC_getTuneThreshold(KRef) {
|
||||
#endif
|
||||
}
|
||||
|
||||
OBJ_GETTER(Kotlin_native_internal_GC_detectCycles, KRef) {
|
||||
if (!KonanNeedDebugInfo || !g_checkLeaks) RETURN_OBJ(nullptr);
|
||||
RETURN_RESULT_OF0(detectCyclicReferences);
|
||||
}
|
||||
|
||||
OBJ_GETTER(Kotlin_native_internal_GC_findCycle, KRef, KRef root) {
|
||||
RETURN_RESULT_OF(findCycle, root);
|
||||
}
|
||||
|
||||
KNativePtr CreateStablePointer(KRef any) {
|
||||
return createStablePointer(any);
|
||||
}
|
||||
|
||||
@@ -315,16 +315,27 @@ ALWAYS_INLINE bool hasPointerBits(T* ptr, unsigned bits) {
|
||||
struct MetaObjHeader {
|
||||
// Pointer to the type info. Must be first, to match ArrayHeader and ObjHeader layout.
|
||||
const TypeInfo* typeInfo_;
|
||||
// Strong reference to the counter object.
|
||||
ObjHeader* counter_;
|
||||
// Container pointer.
|
||||
ContainerHeader* container_;
|
||||
|
||||
#ifdef KONAN_OBJC_INTEROP
|
||||
void* associatedObject_;
|
||||
#endif
|
||||
|
||||
// Flags for the object state.
|
||||
int32_t flags_;
|
||||
|
||||
// TODO: maybe make it a union for the orthogonal features.
|
||||
struct {
|
||||
// Strong reference to the counter object.
|
||||
ObjHeader* counter_;
|
||||
} WeakReference;
|
||||
struct {
|
||||
// Leak detector's previous list element.
|
||||
ObjHeader* previous_;
|
||||
// Leak detector's next list element.
|
||||
ObjHeader* next_;
|
||||
} LeakDetector;
|
||||
};
|
||||
|
||||
// Header of every object.
|
||||
@@ -403,16 +414,16 @@ extern "C" {
|
||||
returnType name(__VA_ARGS__) RUNTIME_NOTHROW; \
|
||||
returnType name##Strict(__VA_ARGS__) RUNTIME_NOTHROW; \
|
||||
returnType name##Relaxed(__VA_ARGS__) RUNTIME_NOTHROW;
|
||||
#define RETURN_OBJ(value) { ObjHeader* obj = value; \
|
||||
UpdateReturnRef(OBJ_RESULT, obj); \
|
||||
return obj; }
|
||||
#define RETURN_OBJ(value) { ObjHeader* __obj = value; \
|
||||
UpdateReturnRef(OBJ_RESULT, __obj); \
|
||||
return __obj; }
|
||||
#define RETURN_RESULT_OF0(name) { \
|
||||
ObjHeader* obj = name(OBJ_RESULT); \
|
||||
return obj; \
|
||||
ObjHeader* __obj = name(OBJ_RESULT); \
|
||||
return __obj; \
|
||||
}
|
||||
#define RETURN_RESULT_OF(name, ...) { \
|
||||
ObjHeader* result = name(__VA_ARGS__, OBJ_RESULT); \
|
||||
return result; \
|
||||
ObjHeader* __result = name(__VA_ARGS__, OBJ_RESULT); \
|
||||
return __result; \
|
||||
}
|
||||
|
||||
struct MemoryState;
|
||||
|
||||
@@ -57,7 +57,8 @@ enum Konan_TypeFlags {
|
||||
TF_IMMUTABLE = 1 << 0,
|
||||
TF_ACYCLIC = 1 << 1,
|
||||
TF_INTERFACE = 1 << 2,
|
||||
TF_OBJC_DYNAMIC = 1 << 3
|
||||
TF_OBJC_DYNAMIC = 1 << 3,
|
||||
TF_LEAK_DETECTOR_CANDIDATE = 1 << 4
|
||||
};
|
||||
|
||||
// Flags per object instance.
|
||||
|
||||
@@ -61,13 +61,13 @@ OBJ_GETTER(Konan_getWeakReferenceImpl, ObjHeader* referred) {
|
||||
}
|
||||
#endif // KONAN_OBJC_INTEROP
|
||||
|
||||
if (meta->counter_ == nullptr) {
|
||||
if (meta->WeakReference.counter_ == nullptr) {
|
||||
ObjHolder counterHolder;
|
||||
// Cast unneeded, just to emphasize we store an object reference as void*.
|
||||
ObjHeader* counter = makeWeakReferenceCounter(reinterpret_cast<void*>(referred), counterHolder.slot());
|
||||
UpdateHeapRefIfNull(&meta->counter_, counter);
|
||||
UpdateHeapRefIfNull(&meta->WeakReference.counter_, counter);
|
||||
}
|
||||
RETURN_OBJ(meta->counter_);
|
||||
RETURN_OBJ(meta->WeakReference.counter_);
|
||||
}
|
||||
|
||||
// Materialize a weak reference to either null or the real reference.
|
||||
|
||||
@@ -7,6 +7,7 @@ package kotlin.native.concurrent
|
||||
|
||||
import kotlin.native.internal.ExportTypeInfo
|
||||
import kotlin.native.internal.Frozen
|
||||
import kotlin.native.internal.LeakDetectorCandidate
|
||||
import kotlin.native.internal.NoReorderFields
|
||||
import kotlin.native.SymbolName
|
||||
import kotlinx.cinterop.NativePtr
|
||||
@@ -204,12 +205,22 @@ public class AtomicNativePtr(private var value_: NativePtr) {
|
||||
private external fun getImpl(): NativePtr
|
||||
}
|
||||
|
||||
|
||||
private fun idString(value: Any) = "${value.hashCode().toUInt().toString(16)}"
|
||||
|
||||
private fun debugString(value: Any?): String {
|
||||
if (value == null) return "null"
|
||||
return "${value::class.qualifiedName}: ${idString(value)}"
|
||||
}
|
||||
|
||||
/**
|
||||
* An atomic reference to a frozen Kotlin object. Can be used in concurrent scenarious
|
||||
* but frequently shall be of nullable type and be zeroed out (with `compareAndSwap(get(), null)`)
|
||||
* once no longer needed. Otherwise memory leak could happen.
|
||||
* but frequently shall be of nullable type and be zeroed out once no longer needed.
|
||||
* Otherwise memory leak could happen. To detect such leaks [kotlin.native.internal.GC.detectCycles]
|
||||
* in debug mode could be helpful.
|
||||
*/
|
||||
@Frozen
|
||||
@LeakDetectorCandidate
|
||||
@NoReorderFields
|
||||
public class AtomicReference<T>(private var value_: T) {
|
||||
// A spinlock to fix potential ARC race.
|
||||
@@ -263,7 +274,8 @@ public class AtomicReference<T>(private var value_: T) {
|
||||
*
|
||||
* @return string representation of this object
|
||||
*/
|
||||
public override fun toString(): String = "Atomic reference to $value"
|
||||
public override fun toString(): String =
|
||||
"${debugString(this)} -> ${debugString(value)}"
|
||||
|
||||
// Implementation details.
|
||||
@SymbolName("Kotlin_AtomicReference_set")
|
||||
@@ -271,15 +283,16 @@ public class AtomicReference<T>(private var value_: T) {
|
||||
|
||||
@SymbolName("Kotlin_AtomicReference_get")
|
||||
private external fun getImpl(): Any?
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* An atomic reference to a Kotlin object. Can be used in concurrent scenarious, but must be frozen first,
|
||||
* otherwise behaves as regular box for the value.
|
||||
* otherwise behaves as regular box for the value. If frozen, shall be zeroed out once no longer needed.
|
||||
* Otherwise memory leak could happen. To detect such leaks [kotlin.native.internal.GC.detectCycles]
|
||||
* in debug mode could be helpful.
|
||||
*/
|
||||
@NoReorderFields
|
||||
@LeakDetectorCandidate
|
||||
@ExportTypeInfo("theFreezableAtomicReferenceTypeInfo")
|
||||
public class FreezableAtomicReference<T>(private var value_: T) {
|
||||
// A spinlock to fix potential ARC race.
|
||||
@@ -342,7 +355,8 @@ public class FreezableAtomicReference<T>(private var value_: T) {
|
||||
*
|
||||
* @return string representation of this object
|
||||
*/
|
||||
public override fun toString(): String = "Freezable atomic reference to $value"
|
||||
public override fun toString(): String =
|
||||
"${debugString(this)} -> ${debugString(value)}"
|
||||
|
||||
// Implementation details.
|
||||
@SymbolName("Kotlin_AtomicReference_set")
|
||||
|
||||
@@ -115,4 +115,10 @@ annotation class Independent
|
||||
*/
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@PublishedApi internal annotation class FilterExceptions
|
||||
@PublishedApi internal annotation class FilterExceptions
|
||||
|
||||
/**
|
||||
* Marks a class whose instances to be added to the list of leak detector candidates.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@PublishedApi internal annotation class LeakDetectorCandidate
|
||||
@@ -78,6 +78,24 @@ object GC {
|
||||
get() = getTuneThreshold()
|
||||
set(value) = setTuneThreshold(value)
|
||||
|
||||
/**
|
||||
* Detect cyclic references going via atomic references and return list of cycle-inducing objects
|
||||
* or `null` if the leak detector is not available. Use [Platform.isMemoryLeakCheckerActive] to check
|
||||
* leak detector availability.
|
||||
* Note that cycle detector requires reference graph stability, thus it may not work as
|
||||
* expected or even crash for mutating graphs.
|
||||
*/
|
||||
@SymbolName("Kotlin_native_internal_GC_detectCycles")
|
||||
external fun detectCycles(): Array<Any>?
|
||||
|
||||
/**
|
||||
* Find a reference cycle including from the given object, `null` if no cycles detected.
|
||||
* Note that cycle detector requires reference graph stability, thus it may not work as
|
||||
* expected or even crash for mutating graphs.
|
||||
*/
|
||||
@SymbolName("Kotlin_native_internal_GC_findCycle")
|
||||
external fun findCycle(root: Any): Array<Any>?
|
||||
|
||||
@SymbolName("Kotlin_native_internal_GC_getThreshold")
|
||||
private external fun getThreshold(): Int
|
||||
|
||||
|
||||
@@ -174,17 +174,21 @@ internal fun getProgressionLast(start: Long, end: Long, step: Long): Long =
|
||||
// Called by the debugger.
|
||||
@ExportForCppRuntime
|
||||
internal fun KonanObjectToUtf8Array(value: Any?): ByteArray {
|
||||
val string = when (value) {
|
||||
is Array<*> -> value.contentToString()
|
||||
is CharArray -> value.contentToString()
|
||||
is BooleanArray -> value.contentToString()
|
||||
is ByteArray -> value.contentToString()
|
||||
is ShortArray -> value.contentToString()
|
||||
is IntArray -> value.contentToString()
|
||||
is LongArray -> value.contentToString()
|
||||
is FloatArray -> value.contentToString()
|
||||
is DoubleArray -> value.contentToString()
|
||||
else -> value.toString()
|
||||
val string = try {
|
||||
when (value) {
|
||||
is Array<*> -> value.contentToString()
|
||||
is CharArray -> value.contentToString()
|
||||
is BooleanArray -> value.contentToString()
|
||||
is ByteArray -> value.contentToString()
|
||||
is ShortArray -> value.contentToString()
|
||||
is IntArray -> value.contentToString()
|
||||
is LongArray -> value.contentToString()
|
||||
is FloatArray -> value.contentToString()
|
||||
is DoubleArray -> value.contentToString()
|
||||
else -> value.toString()
|
||||
}
|
||||
} catch (error: Throwable) {
|
||||
"<Thrown $error when converting to string>"
|
||||
}
|
||||
return string.encodeToByteArray()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user