Fix memory leak in runtime on macOS.
This commit is contained in:
committed by
Nikolay Igotti
parent
b19f5fabba
commit
dcf35ffad2
@@ -2453,6 +2453,7 @@ MemoryState* InitMemory() {
|
||||
}
|
||||
|
||||
void DeinitMemory(MemoryState* memoryState) {
|
||||
::memoryState = memoryState;
|
||||
deinitMemory(memoryState);
|
||||
}
|
||||
|
||||
|
||||
@@ -127,17 +127,18 @@ void consolePrintf(const char* format, ...) {
|
||||
pthread_key_t terminationKey;
|
||||
pthread_once_t terminationKeyOnceControl = PTHREAD_ONCE_INIT;
|
||||
|
||||
typedef void (*destructor_t)();
|
||||
typedef void (*destructor_t)(void*);
|
||||
|
||||
struct DestructorRecord {
|
||||
struct DestructorRecord* next;
|
||||
destructor_t destructor;
|
||||
void* destructorParameter;
|
||||
};
|
||||
|
||||
static void onThreadExitCallback(void* value) {
|
||||
DestructorRecord* record = reinterpret_cast<DestructorRecord*>(value);
|
||||
while (record != nullptr) {
|
||||
record->destructor();
|
||||
record->destructor(record->destructorParameter);
|
||||
auto next = record->next;
|
||||
free(record);
|
||||
record = next;
|
||||
@@ -150,7 +151,7 @@ static void onThreadExitInit() {
|
||||
|
||||
#endif // !KONAN_NO_THREADS
|
||||
|
||||
void onThreadExit(void (*destructor)()) {
|
||||
void onThreadExit(void (*destructor)(void*), void* destructorParameter) {
|
||||
#if KONAN_NO_THREADS
|
||||
#if KONAN_WASM || KONAN_ZEPHYR
|
||||
// No way to do that.
|
||||
@@ -162,6 +163,7 @@ void onThreadExit(void (*destructor)()) {
|
||||
pthread_once(&terminationKeyOnceControl, onThreadExitInit);
|
||||
DestructorRecord* destructorRecord = (DestructorRecord*)calloc(1, sizeof(DestructorRecord));
|
||||
destructorRecord->destructor = destructor;
|
||||
destructorRecord->destructorParameter = destructorParameter;
|
||||
destructorRecord->next =
|
||||
reinterpret_cast<DestructorRecord*>(pthread_getspecific(terminationKey));
|
||||
pthread_setspecific(terminationKey, destructorRecord);
|
||||
|
||||
@@ -37,7 +37,7 @@ RUNTIME_NORETURN void abort(void);
|
||||
RUNTIME_NORETURN void exit(int32_t status);
|
||||
|
||||
// Thread control.
|
||||
void onThreadExit(void (*destructor)());
|
||||
void onThreadExit(void (*destructor)(void*), void* destructorParameter);
|
||||
|
||||
// String/byte operations.
|
||||
// memcpy/memmove/memcmp are not here intentionally, as frequently implemented/optimized
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#include "Porting.h"
|
||||
#include "Runtime.h"
|
||||
|
||||
|
||||
struct RuntimeState {
|
||||
MemoryState* memoryState;
|
||||
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;
|
||||
|
||||
inline bool isValidRuntime() {
|
||||
return ::runtimeState != kInvalidRuntime;
|
||||
}
|
||||
|
||||
int aliveRuntimesCount = 0;
|
||||
|
||||
RuntimeState* initRuntime() {
|
||||
SetKonanTerminateHandler();
|
||||
RuntimeState* result = konanConstructInstance<RuntimeState>();
|
||||
if (!result) return nullptr;
|
||||
RuntimeCheck(runtimeState == nullptr, "No active runtimes allowed");
|
||||
runtimeState = result;
|
||||
if (!result) return kInvalidRuntime;
|
||||
RuntimeCheck(!isValidRuntime(), "No active runtimes allowed");
|
||||
::runtimeState = result;
|
||||
result->memoryState = InitMemory();
|
||||
bool firstRuntime = atomicAdd(&aliveRuntimesCount, 1) == 1;
|
||||
// Keep global variables in state as well.
|
||||
@@ -104,6 +109,12 @@ void deinitRuntime(RuntimeState* 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
|
||||
|
||||
extern "C" {
|
||||
@@ -119,19 +130,18 @@ void AppendToInitializersTail(InitNode *next) {
|
||||
}
|
||||
|
||||
void Kotlin_initRuntimeIfNeeded() {
|
||||
if (runtimeState == nullptr) {
|
||||
if (!isValidRuntime()) {
|
||||
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.
|
||||
konan::onThreadExit(Kotlin_deinitRuntimeIfNeeded);
|
||||
konan::onThreadExit(Kotlin_deinitRuntimeCallback, runtimeState);
|
||||
}
|
||||
}
|
||||
|
||||
void Kotlin_deinitRuntimeIfNeeded() {
|
||||
if (runtimeState != nullptr) {
|
||||
RuntimeCheck(updateStatusIf(runtimeState, RUNNING, DESTROYING), "Cannot transition state to DESTROYING");
|
||||
deinitRuntime(runtimeState);
|
||||
runtimeState = nullptr;
|
||||
if (isValidRuntime()) {
|
||||
deinitRuntime(::runtimeState);
|
||||
::runtimeState = kInvalidRuntime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,23 +155,23 @@ void Kotlin_destroyRuntime(RuntimeState* state) {
|
||||
}
|
||||
|
||||
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;
|
||||
RuntimeCheck(updateStatusIf(result, RUNNING, SUSPENDED), "Cannot transition state to SUSPENDED for suspend");
|
||||
result->memoryState = SuspendMemory();
|
||||
::runtimeState = nullptr;
|
||||
::runtimeState = kInvalidRuntime;
|
||||
return result;
|
||||
}
|
||||
|
||||
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");
|
||||
::runtimeState = state;
|
||||
ResumeMemory(state->memoryState);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -591,8 +591,6 @@ void* workerRoutine(void* argument) {
|
||||
|
||||
konanDestructInstance(worker);
|
||||
|
||||
Kotlin_deinitRuntimeIfNeeded();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,10 @@ external public fun initRuntimeIfNeeded(): Unit
|
||||
|
||||
/**
|
||||
* Deinitializes Kotlin runtime for the current thread, if was inited.
|
||||
* Cannot be called from Kotlin frames holding references, thus deprecated.
|
||||
*/
|
||||
@SymbolName("Kotlin_deinitRuntimeIfNeeded")
|
||||
@Deprecated("Deinit runtime can not be called from Kotlin", level = DeprecationLevel.ERROR)
|
||||
external public fun deinitRuntimeIfNeeded(): Unit
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user