Fix memory leak with frozen objects hanging from cycles.

This commit is contained in:
Nikolay Igotti
2018-03-21 18:33:31 +03:00
committed by Igor Chevdar
parent 62a7c2d2a4
commit 8a06dbfb28
+49 -38
View File
@@ -461,6 +461,35 @@ void DeinitInstanceBody(const TypeInfo* typeInfo, void* body) {
namespace { namespace {
template<typename func>
void traverseContainerObjectFields(ContainerHeader* container, func process) {
ObjHeader* obj = reinterpret_cast<ObjHeader*>(container + 1);
for (int object = 0; object < container->objectCount(); object++) {
const TypeInfo* typeInfo = obj->type_info();
for (int index = 0; index < typeInfo->objOffsetsCount_; index++) {
ObjHeader** location = reinterpret_cast<ObjHeader**>(
reinterpret_cast<uintptr_t>(obj + 1) + typeInfo->objOffsets_[index]);
process(location);
}
if (typeInfo == theArrayTypeInfo) {
ArrayHeader* array = obj->array();
for (int index = 0; index < array->count_; index++) {
process(ArrayAddressOfElementAt(array, index));
}
}
obj = reinterpret_cast<ObjHeader*>(
reinterpret_cast<uintptr_t>(obj) + objectSize(obj));
}
}
template<typename func>
void traverseContainerReferredObjects(ContainerHeader* container, func process) {
traverseContainerObjectFields(container, [process](ObjHeader** location) {
ObjHeader* ref = *location;
if (ref != nullptr) process(ref);
});
}
#if USE_GC #if USE_GC
inline void processFinalizerQueue(MemoryState* state) { inline void processFinalizerQueue(MemoryState* state) {
// TODO: reuse elements of finalizer queue for new allocations. // TODO: reuse elements of finalizer queue for new allocations.
@@ -482,7 +511,16 @@ inline void processFinalizerQueue(MemoryState* state) {
#endif #endif
inline void scheduleDestroyContainer( inline void scheduleDestroyContainer(
MemoryState* state, ContainerHeader* container) { MemoryState* state, ContainerHeader* container, bool clearExternalRefs) {
if (clearExternalRefs) {
traverseContainerObjectFields(reinterpret_cast<ContainerHeader*>(reinterpret_cast<uintptr_t>(container) & ~1),
[](ObjHeader** location) {
ObjHeader* ref = *location;
// Frozen object references do not participate in trial deletion, so shall be explicitly freed.
if (ref != nullptr && ref->container()->frozen())
UpdateRef(location, nullptr);
});
}
#if USE_GC #if USE_GC
state->finalizerQueue->push_front(container); state->finalizerQueue->push_front(container);
// We cannot clean finalizer queue while in GC. // We cannot clean finalizer queue while in GC.
@@ -550,35 +588,6 @@ inline void initThreshold(MemoryState* state, uint32_t gcThreshold) {
} }
#endif // USE_GC #endif // USE_GC
template<typename func>
void traverseContainerObjectFields(ContainerHeader* container, func process) {
ObjHeader* obj = reinterpret_cast<ObjHeader*>(container + 1);
for (int object = 0; object < container->objectCount(); object++) {
const TypeInfo* typeInfo = obj->type_info();
for (int index = 0; index < typeInfo->objOffsetsCount_; index++) {
ObjHeader** location = reinterpret_cast<ObjHeader**>(
reinterpret_cast<uintptr_t>(obj + 1) + typeInfo->objOffsets_[index]);
process(location);
}
if (typeInfo == theArrayTypeInfo) {
ArrayHeader* array = obj->array();
for (int index = 0; index < array->count_; index++) {
process(ArrayAddressOfElementAt(array, index));
}
}
obj = reinterpret_cast<ObjHeader*>(
reinterpret_cast<uintptr_t>(obj) + objectSize(obj));
}
}
template<typename func>
void traverseContainerReferredObjects(ContainerHeader* container, func process) {
traverseContainerObjectFields(container, [process](ObjHeader** location) {
ObjHeader* ref = *location;
if (ref != nullptr) process(ref);
});
}
#if TRACE_MEMORY || USE_GC #if TRACE_MEMORY || USE_GC
void dumpWorker(const char* prefix, ContainerHeader* header, ContainerHeaderSet* seen) { void dumpWorker(const char* prefix, ContainerHeader* header, ContainerHeaderSet* seen) {
@@ -597,7 +606,10 @@ void dumpWorker(const char* prefix, ContainerHeader* header, ContainerHeaderSet*
void dumpReachable(const char* prefix, const ContainerHeaderSet* roots) { void dumpReachable(const char* prefix, const ContainerHeaderSet* roots) {
ContainerHeaderSet seen; ContainerHeaderSet seen;
for (auto container : *roots) { for (auto container : *roots) {
MEMORY_LOG("%p is root\n", container) MEMORY_LOG("%p: %s%s%s\n", container,
container->frozen() ? "frozen " : "",
container->permanent() ? "permanent " : "",
container->stack() ? "stack " : "")
dumpWorker(prefix, container, &seen); dumpWorker(prefix, container, &seen);
} }
} }
@@ -679,7 +691,7 @@ void MarkRoots(MemoryState* state) {
} else { } else {
container->resetBuffered(); container->resetBuffered();
if (color == CONTAINER_TAG_GC_BLACK && rcIsZero) { if (color == CONTAINER_TAG_GC_BLACK && rcIsZero) {
scheduleDestroyContainer(state, reinterpret_cast<ContainerHeader*>(reinterpret_cast<uintptr_t>(container) | 1)); scheduleDestroyContainer(state, reinterpret_cast<ContainerHeader*>(reinterpret_cast<uintptr_t>(container) | 1), true);
} }
} }
} }
@@ -722,11 +734,11 @@ void CollectWhite(MemoryState* state, ContainerHeader* container) {
traverseContainerReferredObjects(container, [state](ObjHeader* ref) { traverseContainerReferredObjects(container, [state](ObjHeader* ref) {
auto childContainer = ref->container(); auto childContainer = ref->container();
RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered"); RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered");
if (!childContainer->permanent()) { if (!childContainer->permanentOrFrozen()) {
CollectWhite(state, childContainer); CollectWhite(state, childContainer);
} }
}); });
scheduleDestroyContainer(state, reinterpret_cast<ContainerHeader*>(reinterpret_cast<uintptr_t>(container) | 1)); scheduleDestroyContainer(state, reinterpret_cast<ContainerHeader*>(reinterpret_cast<uintptr_t>(container) | 1), true);
} }
inline void AddRef(ContainerHeader* header) { inline void AddRef(ContainerHeader* header) {
@@ -832,7 +844,7 @@ void FreeContainer(ContainerHeader* header) {
#if TRACE_MEMORY #if TRACE_MEMORY
memoryState->containers->erase(header); memoryState->containers->erase(header);
#endif #endif
scheduleDestroyContainer(state, header); scheduleDestroyContainer(state, header, false);
} }
} }
} }
@@ -1026,9 +1038,8 @@ void DeinitMemory(MemoryState* memoryState) {
bool lastMemoryState = atomicAdd(&aliveMemoryStatesCount, -1) == 0; bool lastMemoryState = atomicAdd(&aliveMemoryStatesCount, -1) == 0;
#if TRACE_MEMORY #if TRACE_MEMORY
if (allocCount > 0) { if (lastMemoryState && allocCount > 0) {
MEMORY_LOG("*** Memory leaks, leaked %d containers ***\n", MEMORY_LOG("*** Memory leaks, leaked %d containers ***\n", allocCount);
allocCount);
dumpReachable("", memoryState->containers); dumpReachable("", memoryState->containers);
} }
#else #else