diff --git a/backend.native/tests/interop/objc/tests/tryRetainGC.h b/backend.native/tests/interop/objc/tests/tryRetainGC.h new file mode 100644 index 00000000000..e86f91d4ff5 --- /dev/null +++ b/backend.native/tests/interop/objc/tests/tryRetainGC.h @@ -0,0 +1,6 @@ +#import + +@interface WeakRefHolder : NSObject +@property (weak) id obj; +-(void)loadManyTimes; +@end; \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/tryRetainGC.kt b/backend.native/tests/interop/objc/tests/tryRetainGC.kt new file mode 100644 index 00000000000..21f17d5352a --- /dev/null +++ b/backend.native/tests/interop/objc/tests/tryRetainGC.kt @@ -0,0 +1,25 @@ +import kotlinx.cinterop.* +import kotlin.test.* +import objcTests.* + +@Test +fun testTryRetainGC() { + kotlin.native.internal.GC.collect() + val weakRefHolder = WeakRefHolder() + createGarbageNSObjects(weakRefHolder) + weakRefHolder.obj = object : NSObject() {} + // Loading weak ref takes a lock. If K/N runtime runs GC while the lock is taken, + // then it releases garbage objects and thus Obj-C runtime might take a recursive lock + // and abort in _os_unfair_lock_recursive_abort. + weakRefHolder.loadManyTimes() +} + +private fun createGarbageNSObjects(weakRefHolder: WeakRefHolder) { + autoreleasepool { + repeat(100) { + // Assigning the object to a weak reference so Obj-C would take a lock when deallocating it: + weakRefHolder.obj = NSObject() + } + weakRefHolder.obj = null + } +} \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/tryRetainGC.m b/backend.native/tests/interop/objc/tests/tryRetainGC.m new file mode 100644 index 00000000000..b89c9ba26db --- /dev/null +++ b/backend.native/tests/interop/objc/tests/tryRetainGC.m @@ -0,0 +1,11 @@ +#import "tryRetainGC.h" +#import + +@implementation WeakRefHolder +-(void)loadManyTimes { + NSMutableArray* array = [NSMutableArray new]; + for (int i = 0; i < 10000; ++i) { + [array addObject:self.obj]; + } +} +@end; \ No newline at end of file diff --git a/runtime/src/main/cpp/Memory.cpp b/runtime/src/main/cpp/Memory.cpp index 438ad78db5d..a73d78b35b1 100644 --- a/runtime/src/main/cpp/Memory.cpp +++ b/runtime/src/main/cpp/Memory.cpp @@ -1616,23 +1616,23 @@ inline bool tryAddHeapRef(const ObjHeader* header) { return (container != nullptr) ? tryAddHeapRef(container) : true; } -template +template inline void releaseHeapRef(ContainerHeader* container) { MEMORY_LOG("ReleaseHeapRef %p: rc=%d\n", container, container->refCount()) UPDATE_RELEASEREF_STAT(memoryState, container, needAtomicAccess(container), canBeCyclic(container), 0) if (container->tag() != CONTAINER_TAG_STACK) { if (Strict) - enqueueDecrementRC(container); + enqueueDecrementRC(container); else decrementRC(container); } } -template +template inline void releaseHeapRef(const ObjHeader* header) { auto* container = header->container(); if (container != nullptr) - releaseHeapRef(const_cast(container)); + releaseHeapRef(const_cast(container)); } // We use first slot as place to store frame-local arena container. @@ -3135,6 +3135,13 @@ void ReleaseHeapRefRelaxed(const ObjHeader* object) { releaseHeapRef(const_cast(object)); } +void ReleaseHeapRefNoCollectStrict(const ObjHeader* object) { + releaseHeapRef(const_cast(object)); +} +void ReleaseHeapRefNoCollectRelaxed(const ObjHeader* object) { + releaseHeapRef(const_cast(object)); +} + ForeignRefContext InitLocalForeignRef(ObjHeader* object) { return initLocalForeignRef(object); } diff --git a/runtime/src/main/cpp/MemoryPrivate.hpp b/runtime/src/main/cpp/MemoryPrivate.hpp index 17026c4a664..3a5c9296927 100644 --- a/runtime/src/main/cpp/MemoryPrivate.hpp +++ b/runtime/src/main/cpp/MemoryPrivate.hpp @@ -24,6 +24,7 @@ extern "C" { bool TryAddHeapRef(const ObjHeader* object); MODEL_VARIANTS(void, ReleaseHeapRef, const ObjHeader* object); +MODEL_VARIANTS(void, ReleaseHeapRefNoCollect, const ObjHeader* object); void Kotlin_ObjCExport_releaseAssociatedObject(void* associatedObject); diff --git a/runtime/src/main/cpp/MemorySharedRefs.cpp b/runtime/src/main/cpp/MemorySharedRefs.cpp index daad6c61d26..eb5a5e67099 100644 --- a/runtime/src/main/cpp/MemorySharedRefs.cpp +++ b/runtime/src/main/cpp/MemorySharedRefs.cpp @@ -153,7 +153,7 @@ bool BackRefFromAssociatedObject::tryAddRef() { RuntimeAssert(isForeignRefAccessible(obj_, context_), "Cannot be inaccessible because of the check above"); // TODO: This is a very weird way to ask for "unsafe" addRef. addRef(); - ReleaseHeapRef(obj); // Balance TryAddHeapRef. + ReleaseHeapRefNoCollect(obj); // Balance TryAddHeapRef. // TODO: consider optimizing for non-shared objects. return true; diff --git a/runtime/src/relaxed/cpp/MemoryImpl.cpp b/runtime/src/relaxed/cpp/MemoryImpl.cpp index 4cdbda5e3b9..20fe5719ee9 100644 --- a/runtime/src/relaxed/cpp/MemoryImpl.cpp +++ b/runtime/src/relaxed/cpp/MemoryImpl.cpp @@ -33,6 +33,10 @@ void ReleaseHeapRef(const ObjHeader* object) { ReleaseHeapRefRelaxed(object); } +void ReleaseHeapRefNoCollect(const ObjHeader* object) { + ReleaseHeapRefNoCollectRelaxed(object); +} + void ZeroStackRef(ObjHeader** location) { ZeroStackRefRelaxed(location); } diff --git a/runtime/src/strict/cpp/MemoryImpl.cpp b/runtime/src/strict/cpp/MemoryImpl.cpp index 0a5a7afd0a8..0bbcad27a96 100644 --- a/runtime/src/strict/cpp/MemoryImpl.cpp +++ b/runtime/src/strict/cpp/MemoryImpl.cpp @@ -33,6 +33,10 @@ void ReleaseHeapRef(const ObjHeader* object) { ReleaseHeapRefStrict(object); } +void ReleaseHeapRefNoCollect(const ObjHeader* object) { + ReleaseHeapRefNoCollectStrict(object); +} + void SetStackRef(ObjHeader** location, const ObjHeader* object) { SetStackRefStrict(location, object); }