Fix memory leak in runtime on macOS.

This commit is contained in:
Nikolay Igotti
2019-07-10 21:00:08 +03:00
committed by Nikolay Igotti
parent b19f5fabba
commit dcf35ffad2
6 changed files with 35 additions and 22 deletions
+1
View File
@@ -2453,6 +2453,7 @@ MemoryState* InitMemory() {
} }
void DeinitMemory(MemoryState* memoryState) { void DeinitMemory(MemoryState* memoryState) {
::memoryState = memoryState;
deinitMemory(memoryState); deinitMemory(memoryState);
} }
+5 -3
View File
@@ -127,17 +127,18 @@ void consolePrintf(const char* format, ...) {
pthread_key_t terminationKey; pthread_key_t terminationKey;
pthread_once_t terminationKeyOnceControl = PTHREAD_ONCE_INIT; pthread_once_t terminationKeyOnceControl = PTHREAD_ONCE_INIT;
typedef void (*destructor_t)(); typedef void (*destructor_t)(void*);
struct DestructorRecord { struct DestructorRecord {
struct DestructorRecord* next; struct DestructorRecord* next;
destructor_t destructor; destructor_t destructor;
void* destructorParameter;
}; };
static void onThreadExitCallback(void* value) { static void onThreadExitCallback(void* value) {
DestructorRecord* record = reinterpret_cast<DestructorRecord*>(value); DestructorRecord* record = reinterpret_cast<DestructorRecord*>(value);
while (record != nullptr) { while (record != nullptr) {
record->destructor(); record->destructor(record->destructorParameter);
auto next = record->next; auto next = record->next;
free(record); free(record);
record = next; record = next;
@@ -150,7 +151,7 @@ static void onThreadExitInit() {
#endif // !KONAN_NO_THREADS #endif // !KONAN_NO_THREADS
void onThreadExit(void (*destructor)()) { void onThreadExit(void (*destructor)(void*), void* destructorParameter) {
#if KONAN_NO_THREADS #if KONAN_NO_THREADS
#if KONAN_WASM || KONAN_ZEPHYR #if KONAN_WASM || KONAN_ZEPHYR
// No way to do that. // No way to do that.
@@ -162,6 +163,7 @@ void onThreadExit(void (*destructor)()) {
pthread_once(&terminationKeyOnceControl, onThreadExitInit); pthread_once(&terminationKeyOnceControl, onThreadExitInit);
DestructorRecord* destructorRecord = (DestructorRecord*)calloc(1, sizeof(DestructorRecord)); DestructorRecord* destructorRecord = (DestructorRecord*)calloc(1, sizeof(DestructorRecord));
destructorRecord->destructor = destructor; destructorRecord->destructor = destructor;
destructorRecord->destructorParameter = destructorParameter;
destructorRecord->next = destructorRecord->next =
reinterpret_cast<DestructorRecord*>(pthread_getspecific(terminationKey)); reinterpret_cast<DestructorRecord*>(pthread_getspecific(terminationKey));
pthread_setspecific(terminationKey, destructorRecord); pthread_setspecific(terminationKey, destructorRecord);
+1 -1
View File
@@ -37,7 +37,7 @@ RUNTIME_NORETURN void abort(void);
RUNTIME_NORETURN void exit(int32_t status); RUNTIME_NORETURN void exit(int32_t status);
// Thread control. // Thread control.
void onThreadExit(void (*destructor)()); void onThreadExit(void (*destructor)(void*), void* destructorParameter);
// String/byte operations. // String/byte operations.
// memcpy/memmove/memcmp are not here intentionally, as frequently implemented/optimized // memcpy/memmove/memcmp are not here intentionally, as frequently implemented/optimized
+26 -16
View File
@@ -22,7 +22,6 @@
#include "Porting.h" #include "Porting.h"
#include "Runtime.h" #include "Runtime.h"
struct RuntimeState { struct RuntimeState {
MemoryState* memoryState; MemoryState* memoryState;
volatile int executionStatus; volatile int executionStatus;
@@ -72,17 +71,23 @@ void InitOrDeinitGlobalVariables(int initialize) {
} }
} }
THREAD_LOCAL_VARIABLE RuntimeState* runtimeState = nullptr; constexpr RuntimeState* kInvalidRuntime = nullptr;
THREAD_LOCAL_VARIABLE RuntimeState* runtimeState = kInvalidRuntime;
THREAD_LOCAL_VARIABLE int isMainThread = 0; THREAD_LOCAL_VARIABLE int isMainThread = 0;
inline bool isValidRuntime() {
return ::runtimeState != kInvalidRuntime;
}
int aliveRuntimesCount = 0; int aliveRuntimesCount = 0;
RuntimeState* initRuntime() { RuntimeState* initRuntime() {
SetKonanTerminateHandler(); SetKonanTerminateHandler();
RuntimeState* result = konanConstructInstance<RuntimeState>(); RuntimeState* result = konanConstructInstance<RuntimeState>();
if (!result) return nullptr; if (!result) return kInvalidRuntime;
RuntimeCheck(runtimeState == nullptr, "No active runtimes allowed"); RuntimeCheck(!isValidRuntime(), "No active runtimes allowed");
runtimeState = result; ::runtimeState = result;
result->memoryState = InitMemory(); result->memoryState = InitMemory();
bool firstRuntime = atomicAdd(&aliveRuntimesCount, 1) == 1; bool firstRuntime = atomicAdd(&aliveRuntimesCount, 1) == 1;
// Keep global variables in state as well. // Keep global variables in state as well.
@@ -104,6 +109,12 @@ void deinitRuntime(RuntimeState* state) {
konanDestructInstance(state); konanDestructInstance(state);
} }
void Kotlin_deinitRuntimeCallback(void* argument) {
auto* state = reinterpret_cast<RuntimeState*>(argument);
RuntimeCheck(updateStatusIf(state, RUNNING, DESTROYING), "Cannot transition state to DESTROYING");
deinitRuntime(state);
}
} // namespace } // namespace
extern "C" { extern "C" {
@@ -119,19 +130,18 @@ void AppendToInitializersTail(InitNode *next) {
} }
void Kotlin_initRuntimeIfNeeded() { void Kotlin_initRuntimeIfNeeded() {
if (runtimeState == nullptr) { if (!isValidRuntime()) {
initRuntime(); initRuntime();
RuntimeCheck(updateStatusIf(runtimeState, SUSPENDED, RUNNING), "Cannot transition state to RUNNING for init"); RuntimeCheck(updateStatusIf(::runtimeState, SUSPENDED, RUNNING), "Cannot transition state to RUNNING for init");
// Register runtime deinit function at thread cleanup. // Register runtime deinit function at thread cleanup.
konan::onThreadExit(Kotlin_deinitRuntimeIfNeeded); konan::onThreadExit(Kotlin_deinitRuntimeCallback, runtimeState);
} }
} }
void Kotlin_deinitRuntimeIfNeeded() { void Kotlin_deinitRuntimeIfNeeded() {
if (runtimeState != nullptr) { if (isValidRuntime()) {
RuntimeCheck(updateStatusIf(runtimeState, RUNNING, DESTROYING), "Cannot transition state to DESTROYING"); deinitRuntime(::runtimeState);
deinitRuntime(runtimeState); ::runtimeState = kInvalidRuntime;
runtimeState = nullptr;
} }
} }
@@ -145,23 +155,23 @@ void Kotlin_destroyRuntime(RuntimeState* state) {
} }
RuntimeState* Kotlin_suspendRuntime() { RuntimeState* Kotlin_suspendRuntime() {
RuntimeCheck(::runtimeState != nullptr, "Runtime must be active on the current thread"); RuntimeCheck(isValidRuntime(), "Runtime must be active on the current thread");
auto result = ::runtimeState; auto result = ::runtimeState;
RuntimeCheck(updateStatusIf(result, RUNNING, SUSPENDED), "Cannot transition state to SUSPENDED for suspend"); RuntimeCheck(updateStatusIf(result, RUNNING, SUSPENDED), "Cannot transition state to SUSPENDED for suspend");
result->memoryState = SuspendMemory(); result->memoryState = SuspendMemory();
::runtimeState = nullptr; ::runtimeState = kInvalidRuntime;
return result; return result;
} }
void Kotlin_resumeRuntime(RuntimeState* state) { void Kotlin_resumeRuntime(RuntimeState* state) {
RuntimeCheck(::runtimeState == nullptr, "Runtime must not be active on the current thread"); RuntimeCheck(!isValidRuntime(), "Runtime must not be active on the current thread");
RuntimeCheck(updateStatusIf(state, SUSPENDED, RUNNING), "Cannot transition state to RUNNING for resume"); RuntimeCheck(updateStatusIf(state, SUSPENDED, RUNNING), "Cannot transition state to RUNNING for resume");
::runtimeState = state; ::runtimeState = state;
ResumeMemory(state->memoryState); ResumeMemory(state->memoryState);
} }
RuntimeState* RUNTIME_USED Kotlin_getRuntime() { RuntimeState* RUNTIME_USED Kotlin_getRuntime() {
RuntimeCheck(::runtimeState != nullptr, "Runtime must be active on the current thread"); RuntimeCheck(isValidRuntime(), "Runtime must be active on the current thread");
return ::runtimeState; return ::runtimeState;
} }
-2
View File
@@ -591,8 +591,6 @@ void* workerRoutine(void* argument) {
konanDestructInstance(worker); konanDestructInstance(worker);
Kotlin_deinitRuntimeIfNeeded();
return nullptr; return nullptr;
} }
@@ -15,8 +15,10 @@ external public fun initRuntimeIfNeeded(): Unit
/** /**
* Deinitializes Kotlin runtime for the current thread, if was inited. * Deinitializes Kotlin runtime for the current thread, if was inited.
* Cannot be called from Kotlin frames holding references, thus deprecated.
*/ */
@SymbolName("Kotlin_deinitRuntimeIfNeeded") @SymbolName("Kotlin_deinitRuntimeIfNeeded")
@Deprecated("Deinit runtime can not be called from Kotlin", level = DeprecationLevel.ERROR)
external public fun deinitRuntimeIfNeeded(): Unit external public fun deinitRuntimeIfNeeded(): Unit
/** /**