Add memory manager API for foreign reference management

(API based on #3280)
This commit is contained in:
Svyatoslav Scherbina
2019-08-26 15:01:13 +03:00
committed by SvyatoslavScherbina
parent 2cb2f6fe03
commit 215cf05410
3 changed files with 190 additions and 1 deletions
+180 -1
View File
@@ -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<ForeignRefManager>();
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<ListNode>();
newListNode->obj = obj;
while (true) {
ListNode* next = this->releaseList;
newListNode->next = next;
if (compareAndSet(&this->releaseList, next, newListNode)) break;
}
}
template <typename func>
void processEnqueuedReleaseRefsWith(func process) {
if (releaseList == nullptr) return;
ListNode* toProcess = nullptr;
while (true) {
toProcess = releaseList;
if (compareAndSet<ListNode*>(&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<true>(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<false>(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();
+3
View File
@@ -596,4 +596,7 @@ class KRefSharedHolder {
void verifyRefOwner() const;
};
class ForeignRefManager;
typedef ForeignRefManager* ForeignRefContext;
#endif // RUNTIME_MEMORY_H
+7
View File
@@ -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