diff --git a/kotlin-native/runtime/src/launcher/cpp/androidLauncher.cpp b/kotlin-native/runtime/src/launcher/cpp/androidLauncher.cpp index ebbd8a4d636..be29a9776ec 100644 --- a/kotlin-native/runtime/src/launcher/cpp/androidLauncher.cpp +++ b/kotlin-native/runtime/src/launcher/cpp/androidLauncher.cpp @@ -81,6 +81,7 @@ void launchMain() { Konan_start(args.obj()); } + // TODO: Can we shutdown runtime here? Kotlin_deinitRuntimeIfNeeded(); } diff --git a/kotlin-native/runtime/src/launcher/cpp/launcher.cpp b/kotlin-native/runtime/src/launcher/cpp/launcher.cpp index c102a537106..669d40275ed 100644 --- a/kotlin-native/runtime/src/launcher/cpp/launcher.cpp +++ b/kotlin-native/runtime/src/launcher/cpp/launcher.cpp @@ -14,7 +14,6 @@ * limitations under the License. */ -#include "Cleaner.h" #include "Memory.h" #include "Natives.h" #include "Runtime.h" @@ -58,17 +57,7 @@ extern "C" RUNTIME_USED int Init_and_run_start(int argc, const char** argv, int KInt exitStatus = Konan_run_start(argc, argv); if (memoryDeInit) { - if (Kotlin_cleanersLeakCheckerEnabled()) { - // Make sure to collect any lingering cleaners. - PerformFullGC(); - // Execute all the cleaner blocks and stop the Cleaner worker. - ShutdownCleaners(true); - } else { - // Stop the cleaner worker without executing remaining cleaner blocks. - ShutdownCleaners(false); - } - if (Kotlin_memoryLeakCheckerEnabled()) WaitNativeWorkersTermination(); - Kotlin_deinitRuntimeIfNeeded(); + Kotlin_shutdownRuntime(); } return exitStatus; diff --git a/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp b/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp index 7fd0c69bea2..b91b2997eaa 100644 --- a/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp +++ b/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp @@ -3607,8 +3607,8 @@ bool Kotlin_Any_isShareable(KRef thiz) { return thiz == nullptr || isShareable(containerFor(thiz)); } -RUNTIME_NOTHROW void PerformFullGC() { - garbageCollect(::memoryState, true); +RUNTIME_NOTHROW void PerformFullGC(MemoryState* memory) { + garbageCollect(memory, true); } void CheckGlobalsAccessible() { diff --git a/kotlin-native/runtime/src/main/cpp/Memory.h b/kotlin-native/runtime/src/main/cpp/Memory.h index 63c15e49a04..46b45c36b05 100644 --- a/kotlin-native/runtime/src/main/cpp/Memory.h +++ b/kotlin-native/runtime/src/main/cpp/Memory.h @@ -236,7 +236,7 @@ void GC_UnregisterWorker(void* worker) RUNTIME_NOTHROW; void GC_CollectorCallback(void* worker) RUNTIME_NOTHROW; bool Kotlin_Any_isShareable(ObjHeader* thiz); -void PerformFullGC() RUNTIME_NOTHROW; +void PerformFullGC(MemoryState* memory) RUNTIME_NOTHROW; bool TryAddHeapRef(const ObjHeader* object); diff --git a/kotlin-native/runtime/src/main/cpp/Runtime.cpp b/kotlin-native/runtime/src/main/cpp/Runtime.cpp index c169a5d2388..40d45e493c0 100644 --- a/kotlin-native/runtime/src/main/cpp/Runtime.cpp +++ b/kotlin-native/runtime/src/main/cpp/Runtime.cpp @@ -76,12 +76,23 @@ inline bool isValidRuntime() { volatile int aliveRuntimesCount = 0; +enum GlobalRuntimeStatus { + kGlobalRuntimeUninitialized = 0, + kGlobalRuntimeRunning, + kGlobalRuntimeShutdown, +}; + +volatile GlobalRuntimeStatus globalRuntimeStatus = kGlobalRuntimeUninitialized; + RuntimeState* initRuntime() { SetKonanTerminateHandler(); RuntimeState* result = konanConstructInstance(); if (!result) return kInvalidRuntime; RuntimeCheck(!isValidRuntime(), "No active runtimes allowed"); ::runtimeState = result; + + compareAndSwap(&globalRuntimeStatus, kGlobalRuntimeUninitialized, kGlobalRuntimeRunning); + result->memoryState = InitMemory(); result->worker = WorkerInit(true); bool firstRuntime = atomicAdd(&aliveRuntimesCount, 1) == 1; @@ -149,6 +160,34 @@ void Kotlin_deinitRuntimeIfNeeded() { } } +// TODO: Consider exporting it to interop API. +void Kotlin_shutdownRuntime() { + // TODO: If checkers are disabled, we can set status to "shutdown" here, and return. + auto* runtime = ::runtimeState; + RuntimeAssert(runtime != kInvalidRuntime, "Current thread must have Kotlin runtime initialized on it"); + + if (Kotlin_cleanersLeakCheckerEnabled()) { + // Make sure to collect any lingering cleaners. + PerformFullGC(runtime->memoryState); + } + + // Stop cleaner worker. Only execute the cleaners if checker is enabled. + ShutdownCleaners(Kotlin_cleanersLeakCheckerEnabled()); + + // Cleaners are now done, disallow new runtimes. + auto lastStatus = compareAndSwap(&globalRuntimeStatus, kGlobalRuntimeRunning, kGlobalRuntimeShutdown); + RuntimeAssert(lastStatus == kGlobalRuntimeRunning, "Invalid runtime status for shutdown"); + + // TODO: If we add early return at the top, this if would be unneeded. + if (Kotlin_memoryLeakCheckerEnabled() || Kotlin_cleanersLeakCheckerEnabled()) { + // First make sure workers are gone. + WaitNativeWorkersTermination(); + } + + deinitRuntime(runtime); + ::runtimeState = kInvalidRuntime; +} + KInt Konan_Platform_canAccessUnaligned() { #if KONAN_NO_UNALIGNED_ACCESS return 0; diff --git a/kotlin-native/runtime/src/main/cpp/Runtime.h b/kotlin-native/runtime/src/main/cpp/Runtime.h index 8c81d4125ef..2b4e6d152ed 100644 --- a/kotlin-native/runtime/src/main/cpp/Runtime.h +++ b/kotlin-native/runtime/src/main/cpp/Runtime.h @@ -28,6 +28,12 @@ extern "C" { void Kotlin_initRuntimeIfNeeded(); void Kotlin_deinitRuntimeIfNeeded(); +// Can only be called once. +// No new runtimes can be initialized on any thread after this. +// Must be called on a thread with active runtime. +// Using already initialized runtimes on any thread after this is undefined behaviour. +void Kotlin_shutdownRuntime(); + // Appends given node to an initializer list. void AppendToInitializersTail(struct InitNode*); diff --git a/kotlin-native/runtime/src/mm/cpp/Stubs.cpp b/kotlin-native/runtime/src/mm/cpp/Stubs.cpp index e94f82d7b94..cfc6582cd1d 100644 --- a/kotlin-native/runtime/src/mm/cpp/Stubs.cpp +++ b/kotlin-native/runtime/src/mm/cpp/Stubs.cpp @@ -198,7 +198,7 @@ bool Kotlin_Any_isShareable(ObjHeader* thiz) { RuntimeCheck(false, "Unimplemented"); } -RUNTIME_NOTHROW void PerformFullGC() { +RUNTIME_NOTHROW void PerformFullGC(MemoryState* memory) { RuntimeCheck(false, "Unimplemented"); }