Fix KT-33824, ignore FreezableAtomicReference building condensation (#3365)

This commit is contained in:
Nikolay Igotti
2019-09-20 16:26:40 +03:00
committed by GitHub
parent b37465495a
commit 1a2568df38
4 changed files with 202 additions and 38 deletions
+127 -2
View File
@@ -7,6 +7,10 @@ package runtime.workers.freeze6
import kotlin.test.* import kotlin.test.*
import kotlin.native.concurrent.* import kotlin.native.concurrent.*
import kotlin.native.ref.*
data class Hi(val s: String)
data class Nested(val hi: Hi)
@Test @Test
fun ensureNeverFrozenNoFreezeChild(){ fun ensureNeverFrozenNoFreezeChild(){
@@ -30,5 +34,126 @@ fun ensureNeverFrozenFailsTarget(){
println("OK") println("OK")
} }
data class Hi(val s:String) fun createRef1(): FreezableAtomicReference<Any?> {
data class Nested(val hi:Hi) val ref = FreezableAtomicReference<Any?>(null)
ref.value = ref
ref.freeze()
ref.value = null
return ref
}
var global = 0
@Test
fun ensureFreezableHandlesCycles1() {
val ref = createRef1()
kotlin.native.internal.GC.collect()
val obj: Any = ref
global = obj.hashCode()
}
class Node(var ref: Any?)
/**
* ref1 -> Node <- ref3
* | /\
* V |
* ref2 ---
*/
fun createRef2(): Pair<FreezableAtomicReference<Node?>, Any> {
val ref1 = FreezableAtomicReference<Node?>(null)
val node = Node(null)
val ref3 = FreezableAtomicReference<Any?>(node)
val ref2 = FreezableAtomicReference<Any?>(ref3)
node.ref = ref2
ref1.value = node
ref1.freeze()
ref3.value = null
assertTrue(node.isFrozen)
assertTrue(ref1.isFrozen)
assertTrue(ref2.isFrozen)
assertTrue(ref3.isFrozen)
return ref1 to ref2
}
@Test
fun ensureFreezableHandlesCycles2() {
val (ref, obj) = createRef2()
kotlin.native.internal.GC.collect()
assertTrue(obj.toString().length > 0)
global = ref.value!!.ref!!.hashCode()
}
fun createRef3(): FreezableAtomicReference<Any?> {
val ref = FreezableAtomicReference<Any?>(null)
val node = Node(ref)
ref.value = node
ref.freeze()
assertTrue(node.isFrozen)
assertTrue(ref.isFrozen)
return ref
}
@Test
fun ensureFreezableHandlesCycles3() {
val ref = createRef3()
ref.value = null
kotlin.native.internal.GC.collect()
val obj: Any = ref
assertTrue(obj.toString().length > 0)
global = obj.hashCode()
}
lateinit var weakRef: WeakReference<Any>
fun createRef4(): FreezableAtomicReference<Any?> {
val ref = FreezableAtomicReference<Any?>(null)
val node = Node(ref)
weakRef = WeakReference(node)
ref.value = node
ref.freeze()
assertTrue(weakRef.get() != null)
return ref
}
@Test
fun ensureWeakRefNotLeaks1() {
val ref = createRef4()
ref.value = null
// We cannot check weakRef.get() here, as value read will be stored in the stack slot,
// and thus hold weak reference from release.
kotlin.native.internal.GC.collect()
assertTrue(weakRef.get() == null)
}
lateinit var node1: Node
lateinit var weakNode2: WeakReference<Node>
fun createRef5() {
val ref = FreezableAtomicReference<Any?>(null)
node1 = Node(ref)
val node2 = Node(node1)
weakNode2 = WeakReference(node2)
ref.value = node2
node1.freeze()
assertTrue(weakNode2.get() != null)
ref.value = null
}
@Test
fun ensureWeakRefNotLeaks2() {
createRef5()
kotlin.native.internal.GC.collect()
assertTrue(weakNode2.get() == null)
}
+65 -30
View File
@@ -742,6 +742,16 @@ inline bool canFreeze(ContainerHeader* container) {
return container != nullptr && !container->frozen(); return container != nullptr && !container->frozen();
} }
inline bool isFreezableAtomic(ObjHeader* obj) {
return obj->type_info() == theFreezableAtomicReferenceTypeInfo;
}
inline bool isFreezableAtomic(ContainerHeader* container) {
RuntimeAssert(!isAggregatingFrozenContainer(container), "Must be single object");
ObjHeader* obj = reinterpret_cast<ObjHeader*>(container + 1);
return isFreezableAtomic(obj);
}
ContainerHeader* allocContainer(MemoryState* state, size_t size) { ContainerHeader* allocContainer(MemoryState* state, size_t size) {
ContainerHeader* result = nullptr; ContainerHeader* result = nullptr;
#if USE_GC #if USE_GC
@@ -943,7 +953,7 @@ void depthFirstTraversal(ContainerHeader* start, bool* hasCycles,
continue; continue;
} }
toVisit.push_front(markAsRemoved(container)); toVisit.push_front(markAsRemoved(container));
traverseContainerReferredObjects(container, [hasCycles, firstBlocker, &order, &toVisit](ObjHeader* obj) { traverseContainerReferredObjects(container, [container, hasCycles, firstBlocker, &order, &toVisit](ObjHeader* obj) {
if (*firstBlocker != nullptr) if (*firstBlocker != nullptr)
return; return;
if (obj->has_meta_object() && ((obj->meta_object()->flags_ & MF_NEVER_FROZEN) != 0)) { if (obj->has_meta_object() && ((obj->meta_object()->flags_ & MF_NEVER_FROZEN) != 0)) {
@@ -959,7 +969,14 @@ void depthFirstTraversal(ContainerHeader* start, bool* hasCycles,
if (!objContainer->seen() && !objContainer->marked()) { if (!objContainer->seen() && !objContainer->marked()) {
// Mark GRAY. // Mark GRAY.
objContainer->setSeen(); objContainer->setSeen();
toVisit.push_front(objContainer); // Here we do rather interesting trick: when doing DFS we postpone processing references going from
// FreezableAtomic, so that in 'order' referred value will be seen as not actually belonging
// to the same SCC (unless there are other edges not going through FreezableAtomic reaching the same value).
if (isFreezableAtomic(container)) {
toVisit.push_back(objContainer);
} else {
toVisit.push_front(objContainer);
}
} }
} }
}); });
@@ -1503,6 +1520,7 @@ void decrementStack(MemoryState* state) {
while (current < end) { while (current < end) {
ObjHeader* obj = *current++; ObjHeader* obj = *current++;
if (obj != nullptr) { if (obj != nullptr) {
MEMORY_LOG("decrement stack %p\n", obj)
auto* container = obj->container(); auto* container = obj->container();
if (container != nullptr) if (container != nullptr)
enqueueDecrementRC</* CanCollect = */ false>(container); enqueueDecrementRC</* CanCollect = */ false>(container);
@@ -1566,6 +1584,13 @@ void garbageCollect(MemoryState* state, bool force) {
} }
GC_LOG("GC: duration=%lld sinceLast=%lld\n", (gcEndTime - gcStartTime), gcStartTime - state->lastGcTimestamp); GC_LOG("GC: duration=%lld sinceLast=%lld\n", (gcEndTime - gcStartTime), gcStartTime - state->lastGcTimestamp);
state->lastGcTimestamp = gcEndTime; state->lastGcTimestamp = gcEndTime;
#if TRACE_MEMORY
for (auto* obj: *state->toRelease) {
MEMORY_LOG("toRelease %p\n", obj)
}
#endif
GC_LOG("<<< GC: toFree %d toRelease %d\n", state->toFree->size(), state->toRelease->size()) GC_LOG("<<< GC: toFree %d toRelease %d\n", state->toFree->size(), state->toRelease->size())
} }
@@ -2262,56 +2287,65 @@ void freezeAcyclic(ContainerHeader* rootContainer, ContainerHeaderSet* newlyFroz
} }
} }
void freezeCyclic(ContainerHeader* rootContainer, void freezeCyclic(ObjHeader* root,
const KStdVector<ContainerHeader*>& order, const KStdVector<ContainerHeader*>& order,
ContainerHeaderSet* newlyFrozen) { ContainerHeaderSet* newlyFrozen) {
KStdUnorderedMap<ContainerHeader*, KStdVector<ContainerHeader*>> reversedEdges; KStdUnorderedMap<ContainerHeader*, KStdVector<ContainerHeader*>> reversedEdges;
KStdDeque<ContainerHeader*> queue; KStdDeque<ObjHeader*> queue;
queue.push_back(rootContainer); queue.push_back(root);
while (!queue.empty()) { while (!queue.empty()) {
ContainerHeader* current = queue.front(); ObjHeader* current = queue.front();
queue.pop_front(); queue.pop_front();
current->unMark(); ContainerHeader* currentContainer = current->container();
reversedEdges.emplace(current, KStdVector<ContainerHeader*>(0)); currentContainer->unMark();
traverseContainerReferredObjects(current, [current, &queue, &reversedEdges](ObjHeader* obj) { reversedEdges.emplace(currentContainer, KStdVector<ContainerHeader*>(0));
traverseContainerReferredObjects(currentContainer, [current, currentContainer, &queue, &reversedEdges](ObjHeader* obj) {
ContainerHeader* objContainer = obj->container(); ContainerHeader* objContainer = obj->container();
if (canFreeze(objContainer)) { if (canFreeze(objContainer)) {
if (objContainer->marked()) if (objContainer->marked())
queue.push_back(objContainer); queue.push_back(obj);
reversedEdges.emplace(objContainer, KStdVector<ContainerHeader*>(0)).first->second.push_back(current); // We ignore references from FreezableAtomicsReference during condensation, to avoid KT-33824.
if (!isFreezableAtomic(current))
reversedEdges.emplace(objContainer, KStdVector<ContainerHeader*>(0)).
first->second.push_back(currentContainer);
} }
}); });
} }
KStdVector<KStdVector<ContainerHeader*>> components; KStdVector<KStdVector<ContainerHeader*>> components;
MEMORY_LOG("Condensation:\n"); MEMORY_LOG("Condensation:\n");
// Enumerate in the topological order. // Enumerate in the topological order.
for (auto it = order.rbegin(); it != order.rend(); ++it) { for (auto it = order.rbegin(); it != order.rend(); ++it) {
auto* container = *it; auto* container = *it;
if (container->marked()) continue; if (container->marked()) continue;
KStdVector<ContainerHeader*> component; KStdVector<ContainerHeader*> component;
traverseStronglyConnectedComponent(container, &reversedEdges, &component); traverseStronglyConnectedComponent(container, &reversedEdges, &component);
MEMORY_LOG("SCC:\n"); MEMORY_LOG("SCC:\n");
#if TRACE_MEMORY #if TRACE_MEMORY
for (auto c: component) for (auto c: component)
konan::consolePrintf(" %p\n", c); konan::consolePrintf(" %p\n", c);
#endif #endif
components.push_back(std::move(component)); components.push_back(std::move(component));
} }
// Enumerate strongly connected components in reversed topological order. // Enumerate strongly connected components in reversed topological order.
for (auto it = components.rbegin(); it != components.rend(); ++it) { for (auto it = components.rbegin(); it != components.rend(); ++it) {
auto& component = *it; auto& component = *it;
int internalRefsCount = 0; int internalRefsCount = 0;
int totalCount = 0; int totalCount = 0;
for (auto* container : component) { for (auto* container : component) {
RuntimeAssert(!isAggregatingFrozenContainer(container), "Must not be called on such containers");
totalCount += container->refCount(); totalCount += container->refCount();
if (isFreezableAtomic(container)) {
RuntimeAssert(component.size() == 1, "Must be trivial condensation");
continue;
}
traverseContainerReferredObjects(container, [&internalRefsCount](ObjHeader* obj) { traverseContainerReferredObjects(container, [&internalRefsCount](ObjHeader* obj) {
auto* container = obj->container(); auto* container = obj->container();
if (canFreeze(container)) if (canFreeze(container))
++internalRefsCount; ++internalRefsCount;
}); });
} }
// Freeze component. // Freeze component.
for (auto* container : component) { for (auto* container : component) {
@@ -2327,11 +2361,12 @@ void freezeCyclic(ContainerHeader* rootContainer,
// meta-object, where aggregating container is stored. // meta-object, where aggregating container is stored.
container->setRefCount(0); container->setRefCount(0);
} }
// Create fictitious container for the whole component. // Create fictitious container for the whole component.
auto superContainer = component.size() == 1 ? component[0] : allocAggregatingFrozenContainer(component); auto superContainer = component.size() == 1 ? component[0] : allocAggregatingFrozenContainer(component);
// Don't count internal references. // Don't count internal references.
MEMORY_LOG("Setting aggregating %p rc to %d (total %d inner %d)\n", \ MEMORY_LOG("Setting aggregating %p rc to %d (total %d inner %d)\n", \
superContainer, totalCount - internalRefsCount, totalCount, internalRefsCount) superContainer, totalCount - internalRefsCount, totalCount, internalRefsCount)
superContainer->setRefCount(totalCount - internalRefsCount); superContainer->setRefCount(totalCount - internalRefsCount);
newlyFrozen->insert(superContainer); newlyFrozen->insert(superContainer);
} }
@@ -2382,7 +2417,7 @@ void freezeSubgraph(ObjHeader* root) {
ContainerHeaderSet newlyFrozen; ContainerHeaderSet newlyFrozen;
// 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, &newlyFrozen); freezeCyclic(root, order, &newlyFrozen);
} else { } else {
freezeAcyclic(rootContainer, &newlyFrozen); freezeAcyclic(rootContainer, &newlyFrozen);
} }
+8 -6
View File
@@ -81,20 +81,22 @@ extern "C" {
extern const TypeInfo* theAnyTypeInfo; extern const TypeInfo* theAnyTypeInfo;
extern const TypeInfo* theArrayTypeInfo; extern const TypeInfo* theArrayTypeInfo;
extern const TypeInfo* theBooleanArrayTypeInfo;
extern const TypeInfo* theByteArrayTypeInfo; extern const TypeInfo* theByteArrayTypeInfo;
extern const TypeInfo* theCharArrayTypeInfo; extern const TypeInfo* theCharArrayTypeInfo;
extern const TypeInfo* theShortArrayTypeInfo; extern const TypeInfo* theDoubleArrayTypeInfo;
extern const TypeInfo* theForeignObjCObjectTypeInfo;
extern const TypeInfo* theIntArrayTypeInfo; extern const TypeInfo* theIntArrayTypeInfo;
extern const TypeInfo* theLongArrayTypeInfo; extern const TypeInfo* theLongArrayTypeInfo;
extern const TypeInfo* theNativePtrArrayTypeInfo;
extern const TypeInfo* theFloatArrayTypeInfo; extern const TypeInfo* theFloatArrayTypeInfo;
extern const TypeInfo* theDoubleArrayTypeInfo; extern const TypeInfo* theForeignObjCObjectTypeInfo;
extern const TypeInfo* theBooleanArrayTypeInfo; extern const TypeInfo* theFreezableAtomicReferenceTypeInfo;
extern const TypeInfo* theObjCObjectWrapperTypeInfo;
extern const TypeInfo* theShortArrayTypeInfo;
extern const TypeInfo* theStringTypeInfo; extern const TypeInfo* theStringTypeInfo;
extern const TypeInfo* theThrowableTypeInfo; extern const TypeInfo* theThrowableTypeInfo;
extern const TypeInfo* theUnitTypeInfo; extern const TypeInfo* theUnitTypeInfo;
extern const TypeInfo* theForeignObjCObjectTypeInfo;
extern const TypeInfo* theObjCObjectWrapperTypeInfo;
extern const TypeInfo* theNativePtrArrayTypeInfo;
KBoolean IsInstance(const ObjHeader* obj, const TypeInfo* type_info) RUNTIME_PURE; KBoolean IsInstance(const ObjHeader* obj, const TypeInfo* type_info) RUNTIME_PURE;
void CheckCast(const ObjHeader* obj, const TypeInfo* type_info); void CheckCast(const ObjHeader* obj, const TypeInfo* type_info);
@@ -5,6 +5,7 @@
package kotlin.native.concurrent package kotlin.native.concurrent
import kotlin.native.internal.ExportTypeInfo
import kotlin.native.internal.Frozen import kotlin.native.internal.Frozen
import kotlin.native.internal.NoReorderFields import kotlin.native.internal.NoReorderFields
import kotlin.native.SymbolName import kotlin.native.SymbolName
@@ -279,6 +280,7 @@ public class AtomicReference<T>(private var value_: T) {
* otherwise behaves as regular box for the value. * otherwise behaves as regular box for the value.
*/ */
@NoReorderFields @NoReorderFields
@ExportTypeInfo("theFreezableAtomicReferenceTypeInfo")
public class FreezableAtomicReference<T>(private var value_: T) { public class FreezableAtomicReference<T>(private var value_: T) {
// A spinlock to fix potential ARC race. // A spinlock to fix potential ARC race.
private var lock: Int = 0 private var lock: Int = 0