Restore cycle detector (#4286)
Reimagines cycle detector removed in "Remove obsolete leak detector. (#3819)" at aae8441b6e
This commit is contained in:
committed by
GitHub
parent
d8b56cbe6a
commit
4f725387ff
@@ -2824,6 +2824,12 @@ task memory_stable_ref_cross_thread_check(type: KonanLocalTest) {
|
|||||||
source = "runtime/memory/stable_ref_cross_thread_check.kt"
|
source = "runtime/memory/stable_ref_cross_thread_check.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
standaloneTest("cycle_detector") {
|
||||||
|
disabled = project.globalTestArgs.contains('-opt') || (project.testTarget == 'wasm32') // Needs debug build.
|
||||||
|
flags = ['-tr', '-g']
|
||||||
|
source = "runtime/memory/cycle_detector.kt"
|
||||||
|
}
|
||||||
|
|
||||||
standaloneTest("cycle_collector") {
|
standaloneTest("cycle_collector") {
|
||||||
disabled = true // Needs USE_CYCLIC_GC, which is disabled.
|
disabled = true // Needs USE_CYCLIC_GC, which is disabled.
|
||||||
flags = ['-g']
|
flags = ['-g']
|
||||||
|
|||||||
@@ -0,0 +1,193 @@
|
|||||||
|
import kotlin.native.concurrent.*
|
||||||
|
import kotlin.native.internal.GC
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
class Holder(var other: Any?)
|
||||||
|
|
||||||
|
class Holder2(var field1: Any?, var field2: Any?)
|
||||||
|
|
||||||
|
val <T> Array<T>.description: String
|
||||||
|
get() {
|
||||||
|
val result = StringBuilder()
|
||||||
|
result.append('[')
|
||||||
|
for (elem in this) {
|
||||||
|
result.append(elem.toString())
|
||||||
|
result.append(',')
|
||||||
|
}
|
||||||
|
result.append(']')
|
||||||
|
return result.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assertArrayEquals(
|
||||||
|
expected: Array<Any>,
|
||||||
|
actual: Array<Any>
|
||||||
|
): Unit {
|
||||||
|
val lazyMessage: () -> String? = {
|
||||||
|
"Expected <${expected.description}>, actual <${actual.description}>."
|
||||||
|
}
|
||||||
|
|
||||||
|
asserter.assertTrue(lazyMessage, expected.size == actual.size)
|
||||||
|
for (i in expected.indices) {
|
||||||
|
asserter.assertTrue(lazyMessage, expected[i] == actual[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun noCycles() {
|
||||||
|
val atomic1 = AtomicReference<Any?>(null)
|
||||||
|
val atomic2 = AtomicReference<Any?>(null)
|
||||||
|
try {
|
||||||
|
atomic1.value = atomic2
|
||||||
|
val cycles = GC.detectCycles()!!
|
||||||
|
assertEquals(0, cycles.size)
|
||||||
|
assertNull(GC.findCycle(atomic1));
|
||||||
|
assertNull(GC.findCycle(atomic2));
|
||||||
|
} finally {
|
||||||
|
atomic1.value = null
|
||||||
|
atomic2.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun oneCycle() {
|
||||||
|
val atomic = AtomicReference<Any?>(null)
|
||||||
|
try {
|
||||||
|
atomic.value = atomic
|
||||||
|
val cycles = GC.detectCycles()!!
|
||||||
|
assertEquals(1, cycles.size)
|
||||||
|
assertArrayEquals(arrayOf(atomic, atomic), GC.findCycle(cycles[0])!!)
|
||||||
|
} finally {
|
||||||
|
atomic.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun oneCycleWithHolder() {
|
||||||
|
val atomic = AtomicReference<Any?>(null)
|
||||||
|
try {
|
||||||
|
atomic.value = Holder(atomic).freeze()
|
||||||
|
val cycles = GC.detectCycles()!!
|
||||||
|
assertEquals(1, cycles.size)
|
||||||
|
assertArrayEquals(arrayOf(atomic, atomic.value!!, atomic), GC.findCycle(cycles[0])!!)
|
||||||
|
assertArrayEquals(arrayOf(atomic.value!!, atomic, atomic.value!!), GC.findCycle(atomic.value!!)!!)
|
||||||
|
} finally {
|
||||||
|
atomic.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun oneCycleWithArray() {
|
||||||
|
val array = arrayOf(AtomicReference<Any?>(null), AtomicReference<Any?>(null))
|
||||||
|
try {
|
||||||
|
array[0].value = Holder(array).freeze()
|
||||||
|
val cycles = GC.detectCycles()!!
|
||||||
|
assertEquals(1, cycles.size)
|
||||||
|
assertArrayEquals(arrayOf(array[0], array[0].value!!, array, array[0]), GC.findCycle(cycles[0])!!)
|
||||||
|
} finally {
|
||||||
|
array[0].value = null
|
||||||
|
array[1].value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun oneCycleWithLongChain() {
|
||||||
|
val atomic = AtomicReference<Any?>(null)
|
||||||
|
try {
|
||||||
|
val head = Holder(null)
|
||||||
|
var current = head
|
||||||
|
repeat(30) {
|
||||||
|
val next = Holder(null)
|
||||||
|
current.other = next
|
||||||
|
current = next
|
||||||
|
}
|
||||||
|
current.other = atomic
|
||||||
|
atomic.value = head.freeze()
|
||||||
|
val cycles = GC.detectCycles()!!
|
||||||
|
assertEquals(1, cycles.size)
|
||||||
|
val cycle = GC.findCycle(cycles[0])!!
|
||||||
|
assertEquals(33, cycle.size)
|
||||||
|
} finally {
|
||||||
|
atomic.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun twoCycles() {
|
||||||
|
val atomic1 = AtomicReference<Any?>(null)
|
||||||
|
val atomic2 = AtomicReference<Any?>(null)
|
||||||
|
try {
|
||||||
|
atomic1.value = atomic2
|
||||||
|
atomic2.value = atomic1
|
||||||
|
val cycles = GC.detectCycles()!!
|
||||||
|
assertEquals(2, cycles.size)
|
||||||
|
assertArrayEquals(arrayOf(atomic2, atomic1, atomic2), GC.findCycle(cycles[0])!!)
|
||||||
|
assertArrayEquals(arrayOf(atomic1, atomic2, atomic1), GC.findCycle(cycles[1])!!)
|
||||||
|
} finally {
|
||||||
|
atomic1.value = null
|
||||||
|
atomic2.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun twoCyclesWithHolder() {
|
||||||
|
val atomic1 = AtomicReference<Any?>(null)
|
||||||
|
val atomic2 = AtomicReference<Any?>(null)
|
||||||
|
try {
|
||||||
|
atomic1.value = atomic2
|
||||||
|
atomic2.value = Holder(atomic1).freeze()
|
||||||
|
val cycles = GC.detectCycles()!!
|
||||||
|
assertEquals(2, cycles.size)
|
||||||
|
assertArrayEquals(arrayOf(atomic2, atomic2.value!!, atomic1, atomic2), GC.findCycle(cycles[0])!!)
|
||||||
|
assertArrayEquals(arrayOf(atomic1, atomic2, atomic2.value!!, atomic1), GC.findCycle(cycles[1])!!)
|
||||||
|
} finally {
|
||||||
|
atomic1.value = null
|
||||||
|
atomic2.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun threeSeparateCycles() {
|
||||||
|
val atomic1 = AtomicReference<Any?>(null)
|
||||||
|
val atomic2 = AtomicReference<Any?>(null)
|
||||||
|
val atomic3 = AtomicReference<Any?>(null)
|
||||||
|
try {
|
||||||
|
atomic1.value = atomic1
|
||||||
|
atomic2.value = Holder2(atomic1, atomic2).freeze()
|
||||||
|
atomic3.value = Holder2(atomic3, atomic1).freeze()
|
||||||
|
val cycles = GC.detectCycles()!!
|
||||||
|
assertEquals(3, cycles.size)
|
||||||
|
assertArrayEquals(arrayOf(atomic3, atomic3.value!!, atomic3), GC.findCycle(cycles[0])!!)
|
||||||
|
assertArrayEquals(arrayOf(atomic2, atomic2.value!!, atomic2), GC.findCycle(cycles[1])!!)
|
||||||
|
assertArrayEquals(arrayOf(atomic1, atomic1), GC.findCycle(cycles[2])!!)
|
||||||
|
} finally {
|
||||||
|
atomic1.value = null
|
||||||
|
atomic2.value = null
|
||||||
|
atomic3.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun noCyclesWithFreezableAtomicReference() {
|
||||||
|
val atomic = FreezableAtomicReference<Any?>(null)
|
||||||
|
try {
|
||||||
|
atomic.value = atomic
|
||||||
|
val cycles = GC.detectCycles()!!
|
||||||
|
assertEquals(0, cycles.size)
|
||||||
|
} finally {
|
||||||
|
atomic.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun oneCycleWithFrozenFreezableAtomicReference() {
|
||||||
|
val atomic = FreezableAtomicReference<Any?>(null)
|
||||||
|
try {
|
||||||
|
atomic.value = atomic
|
||||||
|
atomic.freeze()
|
||||||
|
val cycles = GC.detectCycles()!!
|
||||||
|
assertEquals(1, cycles.size)
|
||||||
|
assertArrayEquals(arrayOf(atomic, atomic), GC.findCycle(cycles[0])!!)
|
||||||
|
} finally {
|
||||||
|
atomic.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,6 +35,7 @@
|
|||||||
#include "Natives.h"
|
#include "Natives.h"
|
||||||
#include "Porting.h"
|
#include "Porting.h"
|
||||||
#include "Runtime.h"
|
#include "Runtime.h"
|
||||||
|
#include "Utils.h"
|
||||||
#include "WorkerBoundReference.h"
|
#include "WorkerBoundReference.h"
|
||||||
|
|
||||||
// If garbage collection algorithm for cyclic garbage to be used.
|
// If garbage collection algorithm for cyclic garbage to be used.
|
||||||
@@ -132,6 +133,103 @@ volatile int aliveMemoryStatesCount = 0;
|
|||||||
KBoolean g_hasCyclicCollector = true;
|
KBoolean g_hasCyclicCollector = true;
|
||||||
#endif // USE_CYCLIC_GC
|
#endif // USE_CYCLIC_GC
|
||||||
|
|
||||||
|
// TODO: Consider using ObjHolder.
|
||||||
|
class ScopedRefHolder {
|
||||||
|
public:
|
||||||
|
ScopedRefHolder() = default;
|
||||||
|
|
||||||
|
explicit ScopedRefHolder(KRef obj);
|
||||||
|
|
||||||
|
ScopedRefHolder(const ScopedRefHolder&) = delete;
|
||||||
|
|
||||||
|
ScopedRefHolder(ScopedRefHolder&& other) noexcept: obj_(other.obj_) {
|
||||||
|
other.obj_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedRefHolder& operator=(const ScopedRefHolder&) = delete;
|
||||||
|
|
||||||
|
ScopedRefHolder& operator=(ScopedRefHolder&& other) noexcept {
|
||||||
|
ScopedRefHolder tmp(std::move(other));
|
||||||
|
swap(tmp);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
~ScopedRefHolder();
|
||||||
|
|
||||||
|
void swap(ScopedRefHolder& other) noexcept {
|
||||||
|
std::swap(obj_, other.obj_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
KRef obj_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CycleDetectorRootset {
|
||||||
|
// Orders roots.
|
||||||
|
KStdVector<KRef> roots;
|
||||||
|
// Pins a state of each root.
|
||||||
|
KStdUnorderedMap<KRef, KStdVector<KRef>> rootToFields;
|
||||||
|
// Holding roots and their fields to avoid GC-ing them.
|
||||||
|
KStdVector<ScopedRefHolder> heldRefs;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CycleDetector {
|
||||||
|
public:
|
||||||
|
static void insertCandidateIfNeeded(KRef object) {
|
||||||
|
if (canBeACandidate(object))
|
||||||
|
instance().insertCandidate(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void removeCandidateIfNeeded(KRef object) {
|
||||||
|
if (canBeACandidate(object))
|
||||||
|
instance().removeCandidate(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static CycleDetectorRootset collectRootset();
|
||||||
|
|
||||||
|
private:
|
||||||
|
CycleDetector() = default;
|
||||||
|
~CycleDetector() = default;
|
||||||
|
CycleDetector(const CycleDetector&) = delete;
|
||||||
|
CycleDetector(CycleDetector&&) = delete;
|
||||||
|
CycleDetector& operator=(const CycleDetector&) = delete;
|
||||||
|
CycleDetector& operator=(CycleDetector&&) = delete;
|
||||||
|
|
||||||
|
static CycleDetector& instance() {
|
||||||
|
// Only store a pointer to CycleDetector in .bss
|
||||||
|
static CycleDetector* result = new CycleDetector();
|
||||||
|
return *result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool canBeACandidate(KRef object) {
|
||||||
|
return KonanNeedDebugInfo &&
|
||||||
|
Kotlin_memoryLeakCheckerEnabled() &&
|
||||||
|
(object->type_info()->flags_ & TF_LEAK_DETECTOR_CANDIDATE) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void insertCandidate(KRef candidate) {
|
||||||
|
LockGuard<SimpleMutex> guard(lock_);
|
||||||
|
|
||||||
|
auto it = candidateList_.insert(candidateList_.begin(), candidate);
|
||||||
|
candidateInList_.emplace(candidate, it);
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeCandidate(KRef candidate) {
|
||||||
|
LockGuard<SimpleMutex> guard(lock_);
|
||||||
|
|
||||||
|
auto it = candidateInList_.find(candidate);
|
||||||
|
if (it == candidateInList_.end())
|
||||||
|
return;
|
||||||
|
candidateList_.erase(it->second);
|
||||||
|
candidateInList_.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
SimpleMutex lock_;
|
||||||
|
using CandidateList = KStdList<KRef>;
|
||||||
|
CandidateList candidateList_;
|
||||||
|
KStdUnorderedMap<KRef, CandidateList::iterator> candidateInList_;
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: can we pass this variable as an explicit argument?
|
// TODO: can we pass this variable as an explicit argument?
|
||||||
THREAD_LOCAL_VARIABLE MemoryState* memoryState = nullptr;
|
THREAD_LOCAL_VARIABLE MemoryState* memoryState = nullptr;
|
||||||
THREAD_LOCAL_VARIABLE FrameOverlay* currentFrame = nullptr;
|
THREAD_LOCAL_VARIABLE FrameOverlay* currentFrame = nullptr;
|
||||||
@@ -967,6 +1065,7 @@ ALWAYS_INLINE void runDeallocationHooks(ContainerHeader* container) {
|
|||||||
cyclicRemoveAtomicRoot(obj);
|
cyclicRemoveAtomicRoot(obj);
|
||||||
}
|
}
|
||||||
#endif // USE_CYCLIC_GC
|
#endif // USE_CYCLIC_GC
|
||||||
|
CycleDetector::removeCandidateIfNeeded(obj);
|
||||||
if (obj->has_meta_object()) {
|
if (obj->has_meta_object()) {
|
||||||
ObjHeader::destroyMetaObject(&obj->typeInfoOrMeta_);
|
ObjHeader::destroyMetaObject(&obj->typeInfoOrMeta_);
|
||||||
}
|
}
|
||||||
@@ -2036,6 +2135,7 @@ OBJ_GETTER(allocInstance, const TypeInfo* type_info) {
|
|||||||
makeShareable(container.header());
|
makeShareable(container.header());
|
||||||
}
|
}
|
||||||
#endif // USE_GC
|
#endif // USE_GC
|
||||||
|
CycleDetector::insertCandidateIfNeeded(obj);
|
||||||
#if USE_CYCLIC_GC
|
#if USE_CYCLIC_GC
|
||||||
if ((obj->type_info()->flags_ & TF_LEAK_DETECTOR_CANDIDATE) != 0) {
|
if ((obj->type_info()->flags_ & TF_LEAK_DETECTOR_CANDIDATE) != 0) {
|
||||||
// Note: this should be performed after [rememberNewContainer] (above).
|
// Note: this should be performed after [rememberNewContainer] (above).
|
||||||
@@ -2715,6 +2815,125 @@ void shareAny(ObjHeader* obj) {
|
|||||||
container->makeShared();
|
container->makeShared();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ScopedRefHolder::ScopedRefHolder(KRef obj): obj_(obj) {
|
||||||
|
if (obj_) {
|
||||||
|
addHeapRef(obj_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedRefHolder::~ScopedRefHolder() {
|
||||||
|
if (obj_) {
|
||||||
|
ReleaseHeapRef(obj_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
CycleDetectorRootset CycleDetector::collectRootset() {
|
||||||
|
auto& detector = instance();
|
||||||
|
CycleDetectorRootset rootset;
|
||||||
|
LockGuard<SimpleMutex> guard(detector.lock_);
|
||||||
|
for (auto* candidate: detector.candidateList_) {
|
||||||
|
// Only frozen candidates are to be analyzed.
|
||||||
|
if (!isPermanentOrFrozen(candidate))
|
||||||
|
continue;
|
||||||
|
rootset.roots.push_back(candidate);
|
||||||
|
rootset.heldRefs.emplace_back(candidate);
|
||||||
|
traverseReferredObjects(candidate, [&rootset, candidate](KRef field) {
|
||||||
|
rootset.rootToFields[candidate].push_back(field);
|
||||||
|
// TODO: There's currently a race here:
|
||||||
|
// some other thread might null this field and destroy it in GC before
|
||||||
|
// we put it in ScopedRefHolder.
|
||||||
|
rootset.heldRefs.emplace_back(field);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return rootset;
|
||||||
|
}
|
||||||
|
|
||||||
|
KStdVector<KRef> findCycleWithDFS(KRef root, const CycleDetectorRootset& rootset) {
|
||||||
|
auto traverseFields = [&rootset](KRef obj, auto process) {
|
||||||
|
auto it = rootset.rootToFields.find(obj);
|
||||||
|
// If obj is in the rootset, use it's pinned state.
|
||||||
|
if (it != rootset.rootToFields.end()) {
|
||||||
|
const auto& fields = it->second;
|
||||||
|
for (KRef field: fields) {
|
||||||
|
if (field != nullptr) {
|
||||||
|
process(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
traverseReferredObjects(obj, process);
|
||||||
|
};
|
||||||
|
|
||||||
|
KStdVector<KStdVector<KRef>> toVisit;
|
||||||
|
auto appendFieldsToVisit = [&toVisit, &traverseFields](KRef obj, const KStdVector<KRef>& currentPath) {
|
||||||
|
traverseFields(obj, [&toVisit, ¤tPath](KRef field) {
|
||||||
|
auto path = currentPath;
|
||||||
|
path.push_back(field);
|
||||||
|
toVisit.emplace_back(std::move(path));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
appendFieldsToVisit(root, KRefList(1, root));
|
||||||
|
|
||||||
|
KStdUnorderedSet<KRef> seen;
|
||||||
|
seen.insert(root);
|
||||||
|
while (!toVisit.empty()) {
|
||||||
|
KStdVector<KRef> currentPath = std::move(toVisit.back());
|
||||||
|
toVisit.pop_back();
|
||||||
|
KRef node = currentPath[currentPath.size() - 1];
|
||||||
|
|
||||||
|
if (node == root) {
|
||||||
|
// Found a cycle.
|
||||||
|
return currentPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Already traversed this node.
|
||||||
|
if (seen.count(node) != 0)
|
||||||
|
continue;
|
||||||
|
seen.insert(node);
|
||||||
|
|
||||||
|
appendFieldsToVisit(node, currentPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename C>
|
||||||
|
OBJ_GETTER(createAndFillArray, const C& container) {
|
||||||
|
auto* result = AllocArrayInstance(theArrayTypeInfo, container.size(), OBJ_RESULT)->array();
|
||||||
|
KRef* place = ArrayAddressOfElementAt(result, 0);
|
||||||
|
for (KRef it: container) {
|
||||||
|
UpdateHeapRef(place++, it);
|
||||||
|
}
|
||||||
|
RETURN_OBJ(result->obj());
|
||||||
|
}
|
||||||
|
|
||||||
|
OBJ_GETTER0(detectCyclicReferences) {
|
||||||
|
auto rootset = CycleDetector::collectRootset();
|
||||||
|
|
||||||
|
KStdVector<KRef> cyclic;
|
||||||
|
|
||||||
|
for (KRef root: rootset.roots) {
|
||||||
|
if (!findCycleWithDFS(root, rootset).empty()) {
|
||||||
|
cyclic.push_back(root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_RESULT_OF(createAndFillArray, cyclic);
|
||||||
|
}
|
||||||
|
|
||||||
|
OBJ_GETTER(findCycle, KRef root) {
|
||||||
|
auto rootset = CycleDetector::collectRootset();
|
||||||
|
|
||||||
|
auto cycle = findCycleWithDFS(root, rootset);
|
||||||
|
if (cycle.empty()) {
|
||||||
|
RETURN_OBJ(nullptr);
|
||||||
|
}
|
||||||
|
RETURN_RESULT_OF(createAndFillArray, cycle);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
MetaObjHeader* ObjHeader::createMetaObject(TypeInfo** location) {
|
MetaObjHeader* ObjHeader::createMetaObject(TypeInfo** location) {
|
||||||
@@ -3142,6 +3361,15 @@ KBoolean Kotlin_native_internal_GC_getTuneThreshold(KRef) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OBJ_GETTER(Kotlin_native_internal_GC_detectCycles, KRef) {
|
||||||
|
if (!KonanNeedDebugInfo || !Kotlin_memoryLeakCheckerEnabled()) 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) {
|
KNativePtr CreateStablePointer(KRef any) {
|
||||||
return createStablePointer(any);
|
return createStablePointer(any);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <deque>
|
#include <deque>
|
||||||
|
#include <list>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <set>
|
#include <set>
|
||||||
@@ -78,6 +79,8 @@ template<class Key, class Value, class Compare = std::less<Key>>
|
|||||||
using KStdOrderedMap = std::map<Key, Value, Compare, KonanAllocator<std::pair<const Key, Value>>>;
|
using KStdOrderedMap = std::map<Key, Value, Compare, KonanAllocator<std::pair<const Key, Value>>>;
|
||||||
template<class Value>
|
template<class Value>
|
||||||
using KStdVector = std::vector<Value, KonanAllocator<Value>>;
|
using KStdVector = std::vector<Value, KonanAllocator<Value>>;
|
||||||
|
template<class Value>
|
||||||
|
using KStdList = std::list<Value, KonanAllocator<Value>>;
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|||||||
@@ -216,7 +216,8 @@ private fun debugString(value: Any?): String {
|
|||||||
/**
|
/**
|
||||||
* An atomic reference to a frozen Kotlin object. Can be used in concurrent scenarious
|
* 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 once no longer needed.
|
* but frequently shall be of nullable type and be zeroed out once no longer needed.
|
||||||
* Otherwise memory leak could happen if the atomic reference is a part of a reference cycle.
|
* Otherwise memory leak could happen. To detect such leaks [kotlin.native.internal.GC.detectCycles]
|
||||||
|
* in debug mode could be helpful.
|
||||||
*/
|
*/
|
||||||
@Frozen
|
@Frozen
|
||||||
@LeakDetectorCandidate
|
@LeakDetectorCandidate
|
||||||
@@ -293,7 +294,8 @@ public class AtomicReference<T> {
|
|||||||
/**
|
/**
|
||||||
* An atomic reference to a Kotlin object. Can be used in concurrent scenarious, but must be frozen first,
|
* 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. If frozen, shall be zeroed out once no longer needed.
|
* otherwise behaves as regular box for the value. If frozen, shall be zeroed out once no longer needed.
|
||||||
* Otherwise memory leak could happen if atomic reference is a part of a reference cycle.
|
* Otherwise memory leak could happen. To detect such leaks [kotlin.native.internal.GC.detectCycles]
|
||||||
|
* in debug mode could be helpful.
|
||||||
*/
|
*/
|
||||||
@NoReorderFields
|
@NoReorderFields
|
||||||
@LeakDetectorCandidate
|
@LeakDetectorCandidate
|
||||||
|
|||||||
@@ -100,6 +100,20 @@ object GC {
|
|||||||
get() = getCyclicCollectorEnabled()
|
get() = getCyclicCollectorEnabled()
|
||||||
set(value) = setCyclicCollectorEnabled(value)
|
set(value) = setCyclicCollectorEnabled(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.
|
||||||
|
*/
|
||||||
|
@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.
|
||||||
|
*/
|
||||||
|
@SymbolName("Kotlin_native_internal_GC_findCycle")
|
||||||
|
external fun findCycle(root: Any): Array<Any>?
|
||||||
|
|
||||||
@SymbolName("Kotlin_native_internal_GC_getThreshold")
|
@SymbolName("Kotlin_native_internal_GC_getThreshold")
|
||||||
private external fun getThreshold(): Int
|
private external fun getThreshold(): Int
|
||||||
|
|
||||||
@@ -129,4 +143,4 @@ object GC {
|
|||||||
|
|
||||||
@SymbolName("Kotlin_native_internal_GC_setCyclicCollector")
|
@SymbolName("Kotlin_native_internal_GC_setCyclicCollector")
|
||||||
private external fun setCyclicCollectorEnabled(value: Boolean)
|
private external fun setCyclicCollectorEnabled(value: Boolean)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user