From 215cf054106a2c8610f094227e69f1cbc78bdf29 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Mon, 26 Aug 2019 15:01:13 +0300 Subject: [PATCH] Add memory manager API for foreign reference management (API based on #3280) --- runtime/src/main/cpp/Memory.cpp | 181 ++++++++++++++++++++++++- runtime/src/main/cpp/Memory.h | 3 + runtime/src/main/cpp/MemoryPrivate.hpp | 7 + 3 files changed, 190 insertions(+), 1 deletion(-) diff --git a/runtime/src/main/cpp/Memory.cpp b/runtime/src/main/cpp/Memory.cpp index 4e6226e4001..49fe17eee84 100644 --- a/runtime/src/main/cpp/Memory.cpp +++ b/runtime/src/main/cpp/Memory.cpp @@ -289,8 +289,111 @@ inline bool isShareable(ContainerHeader* container) { return container == nullptr || container->shareable(); } +void garbageCollect(); + } // namespace +class ForeignRefManager { + public: + static ForeignRefManager* create() { + ForeignRefManager* result = konanConstructInstance(); + result->addRef(); + return result; + } + + void addRef() { + atomicAdd(&refCount, 1); + } + + void releaseRef() { + if (atomicAdd(&this->refCount, -1) == 0) { + // So the owning MemoryState has abandoned [this]. + // Leaving the queued work items would result in memory leak. + // Luckily current thread has exclusive access to [this], + // so it can process the queue pretending like it takes ownership of all its objects: + this->processAbandoned(); + + konanDestructInstance(this); + } + } + + bool tryReleaseRefOwned() { + if (atomicAdd(&this->refCount, -1) == 0) { + if (this->releaseList != nullptr) { + // There are no more holders of [this] to process the enqueued work items in [releaseRef]. + // Revert the reference counter back and notify the caller to process and then retry: + atomicAdd(&this->refCount, 1); + return false; + } + + konanDestructInstance(this); + } + + return true; + } + + void enqueueReleaseRef(ObjHeader* obj) { + ListNode* newListNode = konanConstructInstance(); + newListNode->obj = obj; + while (true) { + ListNode* next = this->releaseList; + newListNode->next = next; + if (compareAndSet(&this->releaseList, next, newListNode)) break; + } + } + + template + void processEnqueuedReleaseRefsWith(func process) { + if (releaseList == nullptr) return; + + ListNode* toProcess = nullptr; + + while (true) { + toProcess = releaseList; + if (compareAndSet(&this->releaseList, toProcess, nullptr)) break; + } + + while (toProcess != nullptr) { + process(toProcess->obj); + ListNode* next = toProcess->next; + konanDestructInstance(toProcess); + toProcess = next; + } + } + +private: + int refCount; + + struct ListNode { + ObjHeader* obj; + ListNode* next; + }; + + ListNode* volatile releaseList; + + void processAbandoned() { + if (this->releaseList != nullptr) { + bool hadNoRuntimeInitialized = (memoryState == nullptr); + + if (hadNoRuntimeInitialized) { + Kotlin_initRuntimeIfNeeded(); // Required by ReleaseHeapRef. + } + + processEnqueuedReleaseRefsWith([](ObjHeader* obj) { + ReleaseHeapRef(obj); + }); + + if (hadNoRuntimeInitialized) { + // This thread is likely not intended to run Kotlin code. + // In this case it has no chances to process the release-refs enqueued above using + // the general heuristics, so do this manually: + garbageCollect(); + // TODO: how to handle subsequent processAbandoned() calls? + } + } + } +}; + struct MemoryState { #if TRACE_MEMORY // Set of all containers. @@ -321,6 +424,8 @@ struct MemoryState { // Objects to be released. ContainerHeaderList* toRelease; + ForeignRefManager* foreignRefManager; + bool gcErgonomics; uint64_t lastGcTimestamp; @@ -1377,6 +1482,11 @@ void processDecrements(MemoryState* state) { container = realShareableContainer(container); decrementRC(container); } + + state->foreignRefManager->processEnqueuedReleaseRefsWith([](ObjHeader* obj) { + ContainerHeader* container = obj->container(); + if (container != nullptr) decrementRC(container); + }); state->gcSuspendCount--; } @@ -1480,6 +1590,58 @@ void deinitInstanceBody(const TypeInfo* typeInfo, void* body) { } } +ForeignRefManager* initLocalForeignRef(ObjHeader* object) { + if (!IsStrictMemoryModel) return nullptr; + + return memoryState->foreignRefManager; +} + +ForeignRefManager* initForeignRef(ObjHeader* object) { + addHeapRef(object); + + if (!IsStrictMemoryModel) return nullptr; + + // Note: it is possible to return nullptr for shared object as an optimization, + // but this will force the implementation to release objects on uninitialized threads + // which is generally a memory leak. See [deinitForeignRef]. + auto* manager = memoryState->foreignRefManager; + manager->addRef(); + return manager; +} + +bool isForeignRefAccessible(ObjHeader* object, ForeignRefManager* manager) { + if (!IsStrictMemoryModel) return true; + + if (manager == memoryState->foreignRefManager) { + // Note: it is important that this code neither crashes nor returns false-negative result + // (although may produce false-positive one) if [manager] is a dangling pointer. + // See BackRefFromAssociatedObject::releaseRef for more details. + return true; + } + + // Note: getting container and checking it with 'isShareable()' is supposed to be correct even for unowned object. + return isShareable(object->container()); +} + +void deinitForeignRef(ObjHeader* object, ForeignRefManager* manager) { + if (IsStrictMemoryModel) { + if (memoryState != nullptr && isForeignRefAccessible(object, manager)) { + releaseHeapRef(object); + } else { + // Prefer this for (memoryState == nullptr) since otherwise the object may leak: + // an uninitialized thread did not run any Kotlin code; + // it may be an externally-managed thread which is not supposed to run Kotlin code + // and not going to exit soon. + manager->enqueueReleaseRef(object); + } + + manager->releaseRef(); + } else { + releaseHeapRef(object); + RuntimeAssert(manager == nullptr, "must be null"); + } +} + MemoryState* initMemory() { RuntimeAssert(offsetof(ArrayHeader, typeInfoOrMeta_) == @@ -1503,6 +1665,7 @@ MemoryState* initMemory() { memoryState->allocSinceLastGcThreshold = kMaxGcAllocThreshold; memoryState->gcErgonomics = true; #endif + memoryState->foreignRefManager = ForeignRefManager::create(); atomicAdd(&aliveMemoryStatesCount, 1); return memoryState; } @@ -1513,7 +1676,7 @@ void deinitMemory(MemoryState* memoryState) { do { GC_LOG("Calling garbageCollect from DeinitMemory()\n") garbageCollect(memoryState, true); - } while (memoryState->toRelease->size() > 0); + } while (memoryState->toRelease->size() > 0 || !memoryState->foreignRefManager->tryReleaseRefOwned()); RuntimeAssert(memoryState->toFree->size() == 0, "Some memory have not been released after GC"); RuntimeAssert(memoryState->toRelease->size() == 0, "Some memory have not been released after GC"); konanDestructInstance(memoryState->toFree); @@ -2482,6 +2645,22 @@ void DeinitInstanceBody(const TypeInfo* typeInfo, void* body) { deinitInstanceBody(typeInfo, body); } +ForeignRefContext InitLocalForeignRef(ObjHeader* object) { + return initLocalForeignRef(object); +} + +ForeignRefContext InitForeignRef(ObjHeader* object) { + return initForeignRef(object); +} + +void DeinitForeignRef(ObjHeader* object, ForeignRefContext context) { + deinitForeignRef(object, context); +} + +bool IsForeignRefAccessible(ObjHeader* object, ForeignRefContext context) { + return isForeignRefAccessible(object, context); +} + // Public memory interface. MemoryState* InitMemory() { return initMemory(); diff --git a/runtime/src/main/cpp/Memory.h b/runtime/src/main/cpp/Memory.h index 936f2871b52..082844e839e 100644 --- a/runtime/src/main/cpp/Memory.h +++ b/runtime/src/main/cpp/Memory.h @@ -596,4 +596,7 @@ class KRefSharedHolder { void verifyRefOwner() const; }; +class ForeignRefManager; +typedef ForeignRefManager* ForeignRefContext; + #endif // RUNTIME_MEMORY_H diff --git a/runtime/src/main/cpp/MemoryPrivate.hpp b/runtime/src/main/cpp/MemoryPrivate.hpp index 7e43de9820f..5d2fbab5239 100644 --- a/runtime/src/main/cpp/MemoryPrivate.hpp +++ b/runtime/src/main/cpp/MemoryPrivate.hpp @@ -29,6 +29,13 @@ void DeinitInstanceBody(const TypeInfo* typeInfo, void* body); void Kotlin_ObjCExport_releaseAssociatedObject(void* associatedObject); +ForeignRefContext InitLocalForeignRef(ObjHeader* object); + +ForeignRefContext InitForeignRef(ObjHeader* object); +void DeinitForeignRef(ObjHeader* object, ForeignRefContext context); + +bool IsForeignRefAccessible(ObjHeader* object, ForeignRefContext context); + } // extern "C" #endif // RUNTIME_MEMORYPRIVATE_HPP