Add a switch to destroy runtime only on shutdown (#4482)
This commit is contained in:
committed by
Stanislav Erokhin
parent
37ff2c338e
commit
ee508efb23
@@ -250,6 +250,14 @@ class K2Native : CLICompiler<K2NativeCompilerArguments>() {
|
||||
put(DISABLE_FAKE_OVERRIDE_VALIDATOR, arguments.disableFakeOverrideValidator)
|
||||
putIfNotNull(PRE_LINK_CACHES, parsePreLinkCachesValue(configuration, arguments.preLinkCaches))
|
||||
putIfNotNull(OVERRIDE_KONAN_PROPERTIES, parseOverrideKonanProperties(arguments, configuration))
|
||||
put(DESTROY_RUNTIME_MODE, when (arguments.destroyRuntimeMode) {
|
||||
"legacy" -> DestroyRuntimeMode.LEGACY
|
||||
"on-shutdown" -> DestroyRuntimeMode.ON_SHUTDOWN
|
||||
else -> {
|
||||
configuration.report(ERROR, "Unsupported destroy runtime mode ${arguments.destroyRuntimeMode}")
|
||||
DestroyRuntimeMode.ON_SHUTDOWN
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -285,6 +285,9 @@ class K2NativeCompilerArguments : CommonCompilerArguments() {
|
||||
)
|
||||
var overrideKonanProperties: Array<String>? = null
|
||||
|
||||
@Argument(value="-Xdestroy-runtime-mode", valueDescription = "<mode>", description = "When to destroy runtime. 'legacy' and 'on-shutdown' are currently supported. NOTE: 'legacy' mode is deprecated and will be removed.")
|
||||
var destroyRuntimeMode: String? = "on-shutdown"
|
||||
|
||||
override fun configureAnalysisFlags(collector: MessageCollector): MutableMap<AnalysisFlag<*>, Any> =
|
||||
super.configureAnalysisFlags(collector).also {
|
||||
val useExperimental = it[AnalysisFlags.useExperimental] as List<*>
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
package org.jetbrains.kotlin.backend.konan
|
||||
|
||||
// Must match `DestroyRuntimeMode` in Runtime.h
|
||||
enum class DestroyRuntimeMode(val value: Int) {
|
||||
LEGACY(0),
|
||||
ON_SHUTDOWN(1),
|
||||
}
|
||||
+5
@@ -45,6 +45,7 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
|
||||
?: target.family.isAppleFamily // Default is true for Apple targets.
|
||||
|
||||
val memoryModel: MemoryModel get() = configuration.get(KonanConfigKeys.MEMORY_MODEL)!!
|
||||
val destroyRuntimeMode: DestroyRuntimeMode get() = configuration.get(KonanConfigKeys.DESTROY_RUNTIME_MODE)!!
|
||||
|
||||
val needCompilerVerification: Boolean
|
||||
get() = configuration.get(KonanConfigKeys.VERIFY_COMPILER) ?:
|
||||
@@ -128,6 +129,10 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
|
||||
configuration.report(CompilerMessageSeverity.STRONG_WARNING,
|
||||
"Experimental memory model requires threads, which are not supported on target ${target.name}. Used strict memory model.")
|
||||
MemoryModel.STRICT
|
||||
} else if (destroyRuntimeMode == DestroyRuntimeMode.LEGACY) {
|
||||
configuration.report(CompilerMessageSeverity.STRONG_WARNING,
|
||||
"Experimental memory model is incompatible with 'legacy' destroy runtime mode. Used strict memory model.")
|
||||
MemoryModel.STRICT
|
||||
} else {
|
||||
MemoryModel.EXPERIMENTAL
|
||||
}
|
||||
|
||||
+2
@@ -144,6 +144,8 @@ class KonanConfigKeys {
|
||||
= CompilerConfigurationKey.create("perform compiler caches pre-link")
|
||||
val OVERRIDE_KONAN_PROPERTIES: CompilerConfigurationKey<Map<String, String>>
|
||||
= CompilerConfigurationKey.create("override konan.properties values")
|
||||
val DESTROY_RUNTIME_MODE: CompilerConfigurationKey<DestroyRuntimeMode>
|
||||
= CompilerConfigurationKey.create("when to destroy runtime")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+35
@@ -365,6 +365,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
||||
|
||||
context.coverage.writeRegionInfo()
|
||||
appendDebugSelector()
|
||||
overrideRuntimeGlobals()
|
||||
appendLlvmUsed("llvm.used", context.llvm.usedFunctions + context.llvm.usedGlobals)
|
||||
appendLlvmUsed("llvm.compiler.used", context.llvm.compilerUsedGlobals)
|
||||
if (context.isNativeLibrary) {
|
||||
@@ -2380,6 +2381,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
||||
LLVMSetSection(llvmUsedGlobal.llvmGlobal, "llvm.metadata")
|
||||
}
|
||||
|
||||
// TODO: Consider migrating `KonanNeedDebugInfo` to the `overrideRuntimeGlobal` mechanism from below.
|
||||
private fun appendDebugSelector() {
|
||||
if (!context.producedLlvmModuleContainsStdlib) return
|
||||
val llvmDebugSelector =
|
||||
@@ -2389,6 +2391,39 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
||||
llvmDebugSelector.setLinkage(LLVMLinkage.LLVMExternalLinkage)
|
||||
}
|
||||
|
||||
private fun overrideRuntimeGlobal(name: String, value: ConstValue) {
|
||||
// TODO: A similar mechanism is used in `ObjCExportCodeGenerator`. Consider merging them.
|
||||
if (context.llvmModuleSpecification.importsKotlinDeclarationsFromOtherSharedLibraries()) {
|
||||
// When some dynamic caches are used, we consider that stdlib is in the dynamic cache as well.
|
||||
// Runtime is linked into stdlib module only, so import runtime global from it.
|
||||
val global = codegen.importGlobal(name, value.llvmType, context.standardLlvmSymbolsOrigin)
|
||||
val initializer = generateFunction(codegen, functionType(voidType, false), "") {
|
||||
store(value.llvm, global)
|
||||
ret(null)
|
||||
}
|
||||
|
||||
LLVMSetLinkage(initializer, LLVMLinkage.LLVMPrivateLinkage)
|
||||
|
||||
context.llvm.otherStaticInitializers += initializer
|
||||
} else {
|
||||
context.llvmImports.add(context.standardLlvmSymbolsOrigin)
|
||||
// Define a strong runtime global. It'll overrule a weak global defined in a statically linked runtime.
|
||||
val global = context.llvm.staticData.placeGlobal(name, value, true)
|
||||
|
||||
if (context.llvmModuleSpecification.importsKotlinDeclarationsFromOtherObjectFiles()) {
|
||||
context.llvm.usedGlobals += global.llvmGlobal
|
||||
LLVMSetVisibility(global.llvmGlobal, LLVMVisibility.LLVMHiddenVisibility)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun overrideRuntimeGlobals() {
|
||||
if (!context.config.produce.isFinalBinary)
|
||||
return
|
||||
|
||||
overrideRuntimeGlobal("Kotlin_destroyRuntimeMode", Int32(context.config.destroyRuntimeMode.value))
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------//
|
||||
// Create type { i32, void ()*, i8* }
|
||||
|
||||
|
||||
+1
@@ -474,6 +474,7 @@ private fun ObjCExportCodeGenerator.replaceExternalWeakOrCommonGlobal(
|
||||
value: ConstValue,
|
||||
origin: CompiledKlibModuleOrigin
|
||||
) {
|
||||
// TODO: A similar mechanism is used in `IrToBitcode.overrideRuntimeGlobal`. Consider merging them.
|
||||
if (context.llvmModuleSpecification.importsKotlinDeclarationsFromOtherSharedLibraries()) {
|
||||
val global = codegen.importGlobal(name, value.llvmType, origin)
|
||||
externalGlobalInitializers[global] = value
|
||||
|
||||
@@ -4361,9 +4361,19 @@ dynamicTest("interop_cleaners_leak") {
|
||||
flags = ['-Xopt-in=kotlin.native.internal.InternalForKotlinNative']
|
||||
}
|
||||
|
||||
dynamicTest("interop_migrating_main_thread_legacy") {
|
||||
disabled = (project.target.name != project.hostName)
|
||||
source = "interop/migrating_main_thread/lib.kt"
|
||||
flags = ['-Xdestroy-runtime-mode=legacy']
|
||||
clangFlags = ['-DIS_LEGACY']
|
||||
cSource = "$projectDir/interop/migrating_main_thread/main.cpp"
|
||||
clangTool = "clang++"
|
||||
}
|
||||
|
||||
dynamicTest("interop_migrating_main_thread") {
|
||||
disabled = (project.target.name != project.hostName)
|
||||
source = "interop/migrating_main_thread/lib.kt"
|
||||
flags = ['-Xdestroy-runtime-mode=on-shutdown']
|
||||
cSource = "$projectDir/interop/migrating_main_thread/main.cpp"
|
||||
clangTool = "clang++"
|
||||
}
|
||||
@@ -4374,7 +4384,7 @@ dynamicTest("interop_memory_leaks") {
|
||||
source = "interop/memory_leaks/lib.kt"
|
||||
cSource = "$projectDir/interop/memory_leaks/main.cpp"
|
||||
clangTool = "clang++"
|
||||
flags = ['-g']
|
||||
flags = ['-g', '-Xdestroy-runtime-mode=legacy'] // Runtime cannot be destroyed with interop with on-shutdown.
|
||||
expectedExitStatusChecker = { it != 0 }
|
||||
outputChecker = { s -> s.contains("Memory leaks detected, 1 objects leaked!") }
|
||||
}
|
||||
|
||||
@@ -13,4 +13,10 @@ fun writeToA(i: Int) {
|
||||
globalA.i = i
|
||||
}
|
||||
|
||||
fun readFromA() = globalA.i
|
||||
fun tryReadFromA(default: Int): Int {
|
||||
return try {
|
||||
globalA.i
|
||||
} catch (e: IncorrectDereferenceException) {
|
||||
default
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,17 +8,26 @@
|
||||
#include <cassert>
|
||||
#include <thread>
|
||||
|
||||
constexpr int kInitialValue = 0;
|
||||
constexpr int kNewValue = 1;
|
||||
constexpr int kErrorValue = 2;
|
||||
|
||||
int main() {
|
||||
std::thread main1([]() {
|
||||
assert(testlib_symbols()->kotlin.root.readFromA() == 0);
|
||||
testlib_symbols()->kotlin.root.writeToA(1);
|
||||
assert(testlib_symbols()->kotlin.root.readFromA() == 1);
|
||||
assert(testlib_symbols()->kotlin.root.tryReadFromA(kErrorValue) == kInitialValue);
|
||||
testlib_symbols()->kotlin.root.writeToA(kNewValue);
|
||||
assert(testlib_symbols()->kotlin.root.tryReadFromA(kErrorValue) == kNewValue);
|
||||
});
|
||||
main1.join();
|
||||
|
||||
std::thread main2([]() {
|
||||
#if defined(IS_LEGACY)
|
||||
// Globals were reinitialized.
|
||||
assert(testlib_symbols()->kotlin.root.readFromA() == 0);
|
||||
assert(testlib_symbols()->kotlin.root.tryReadFromA(kErrorValue) == kInitialValue);
|
||||
#else
|
||||
// Globals are not accessible.
|
||||
assert(testlib_symbols()->kotlin.root.tryReadFromA(kErrorValue) == kErrorValue);
|
||||
#endif
|
||||
});
|
||||
main2.join();
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import kotlin.native.ref.*
|
||||
import kotlin.test.*
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
// Test relies on full deinitialization at shutdown.
|
||||
kotlin.native.internal.Debugging.forceCheckedShutdown = true
|
||||
autoreleasepool {
|
||||
run()
|
||||
}
|
||||
|
||||
@@ -419,8 +419,12 @@ open class KonanDynamicTest : KonanStandaloneTest() {
|
||||
@Input
|
||||
lateinit var cSource: String
|
||||
|
||||
@Input
|
||||
var clangTool = "clang"
|
||||
|
||||
@Input
|
||||
var clangFlags: List<String> = listOf()
|
||||
|
||||
// Replace testlib_api.h and all occurrences of the testlib with the actual name of the test
|
||||
private fun processCSource(): String {
|
||||
val sourceFile = File(cSource)
|
||||
@@ -453,7 +457,7 @@ open class KonanDynamicTest : KonanStandaloneTest() {
|
||||
"-c",
|
||||
"-o", "$executable.o",
|
||||
"-I", artifactsDir
|
||||
)
|
||||
) + clangFlags
|
||||
it.standardOutput = log
|
||||
it.errorOutput = log
|
||||
it.isIgnoreExitValue = true
|
||||
@@ -485,4 +489,4 @@ open class KonanDynamicTest : KonanStandaloneTest() {
|
||||
it.execute()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -576,7 +576,7 @@ private:
|
||||
if (atomicGet(&aliveMemoryStatesCount) == 0)
|
||||
return;
|
||||
|
||||
memoryState = InitMemory(); // Required by ReleaseHeapRef.
|
||||
memoryState = InitMemory(false); // Required by ReleaseHeapRef.
|
||||
}
|
||||
|
||||
processEnqueuedReleaseRefsWith([](ObjHeader* obj) {
|
||||
@@ -585,7 +585,7 @@ private:
|
||||
|
||||
if (hadNoStateInitialized) {
|
||||
// Discard the memory state.
|
||||
DeinitMemory(memoryState);
|
||||
DeinitMemory(memoryState, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1978,7 +1978,7 @@ void deinitForeignRef(ObjHeader* object, ForeignRefManager* manager) {
|
||||
}
|
||||
}
|
||||
|
||||
MemoryState* initMemory() {
|
||||
MemoryState* initMemory(bool firstRuntime) {
|
||||
RuntimeAssert(offsetof(ArrayHeader, typeInfoOrMeta_)
|
||||
==
|
||||
offsetof(ObjHeader, typeInfoOrMeta_),
|
||||
@@ -2005,7 +2005,15 @@ MemoryState* initMemory() {
|
||||
memoryState->tlsMap = konanConstructInstance<KThreadLocalStorageMap>();
|
||||
memoryState->foreignRefManager = ForeignRefManager::create();
|
||||
bool firstMemoryState = atomicAdd(&aliveMemoryStatesCount, 1) == 1;
|
||||
if (firstMemoryState) {
|
||||
switch (Kotlin_getDestroyRuntimeMode()) {
|
||||
case DESTROY_RUNTIME_LEGACY:
|
||||
firstRuntime = firstMemoryState;
|
||||
break;
|
||||
case DESTROY_RUNTIME_ON_SHUTDOWN:
|
||||
// Nothing to do.
|
||||
break;
|
||||
}
|
||||
if (firstRuntime) {
|
||||
#if USE_CYCLIC_GC
|
||||
cyclicInit();
|
||||
#endif // USE_CYCLIC_GC
|
||||
@@ -2014,13 +2022,21 @@ MemoryState* initMemory() {
|
||||
return memoryState;
|
||||
}
|
||||
|
||||
void deinitMemory(MemoryState* memoryState) {
|
||||
void deinitMemory(MemoryState* memoryState, bool destroyRuntime) {
|
||||
static int pendingDeinit = 0;
|
||||
atomicAdd(&pendingDeinit, 1);
|
||||
#if USE_GC
|
||||
bool lastMemoryState = atomicAdd(&aliveMemoryStatesCount, -1) == 0;
|
||||
bool checkLeaks = Kotlin_memoryLeakCheckerEnabled() && lastMemoryState;
|
||||
if (lastMemoryState) {
|
||||
switch (Kotlin_getDestroyRuntimeMode()) {
|
||||
case DESTROY_RUNTIME_LEGACY:
|
||||
destroyRuntime = lastMemoryState;
|
||||
break;
|
||||
case DESTROY_RUNTIME_ON_SHUTDOWN:
|
||||
// Nothing to do
|
||||
break;
|
||||
}
|
||||
bool checkLeaks = Kotlin_memoryLeakCheckerEnabled() && destroyRuntime;
|
||||
if (destroyRuntime) {
|
||||
garbageCollect(memoryState, true);
|
||||
#if USE_CYCLIC_GC
|
||||
// If there are other pending deinits (rare situation) - just skip the leak checker.
|
||||
@@ -2051,7 +2067,7 @@ void deinitMemory(MemoryState* memoryState) {
|
||||
atomicAdd(&pendingDeinit, -1);
|
||||
|
||||
#if TRACE_MEMORY
|
||||
if (IsStrictMemoryModel && lastMemoryState && allocCount > 0) {
|
||||
if (IsStrictMemoryModel && destroyRuntime && allocCount > 0) {
|
||||
MEMORY_LOG("*** Memory leaks, leaked %d containers ***\n", allocCount);
|
||||
dumpReachable("", memoryState->containers);
|
||||
}
|
||||
@@ -3231,12 +3247,12 @@ void AdoptReferenceFromSharedVariable(ObjHeader* object) {
|
||||
}
|
||||
|
||||
// Public memory interface.
|
||||
MemoryState* InitMemory() {
|
||||
return initMemory();
|
||||
MemoryState* InitMemory(bool firstRuntime) {
|
||||
return initMemory(firstRuntime);
|
||||
}
|
||||
|
||||
void DeinitMemory(MemoryState* memoryState) {
|
||||
deinitMemory(memoryState);
|
||||
void DeinitMemory(MemoryState* memoryState, bool destroyRuntime) {
|
||||
deinitMemory(memoryState, destroyRuntime);
|
||||
}
|
||||
|
||||
void RestoreMemory(MemoryState* memoryState) {
|
||||
|
||||
@@ -122,8 +122,8 @@ extern "C" {
|
||||
|
||||
struct MemoryState;
|
||||
|
||||
MemoryState* InitMemory();
|
||||
void DeinitMemory(MemoryState*);
|
||||
MemoryState* InitMemory(bool firstRuntime);
|
||||
void DeinitMemory(MemoryState*, bool destroyRuntime);
|
||||
void RestoreMemory(MemoryState*);
|
||||
|
||||
//
|
||||
|
||||
@@ -31,6 +31,13 @@ struct InitNode {
|
||||
InitNode* next;
|
||||
};
|
||||
|
||||
// This global is overriden by the compiler.
|
||||
RUNTIME_WEAK DestroyRuntimeMode Kotlin_destroyRuntimeMode = DESTROY_RUNTIME_ON_SHUTDOWN;
|
||||
|
||||
DestroyRuntimeMode Kotlin_getDestroyRuntimeMode() {
|
||||
return Kotlin_destroyRuntimeMode;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
InitNode* initHeadNode = nullptr;
|
||||
@@ -65,6 +72,7 @@ void InitOrDeinitGlobalVariables(int initialize, MemoryState* memory) {
|
||||
|
||||
KBoolean g_checkLeaks = KonanNeedDebugInfo;
|
||||
KBoolean g_checkLeakedCleaners = KonanNeedDebugInfo;
|
||||
KBoolean g_forceCheckedShutdown = false;
|
||||
|
||||
constexpr RuntimeState* kInvalidRuntime = nullptr;
|
||||
|
||||
@@ -91,11 +99,27 @@ RuntimeState* initRuntime() {
|
||||
RuntimeCheck(!isValidRuntime(), "No active runtimes allowed");
|
||||
::runtimeState = result;
|
||||
|
||||
compareAndSwap(&globalRuntimeStatus, kGlobalRuntimeUninitialized, kGlobalRuntimeRunning);
|
||||
bool firstRuntime = false;
|
||||
switch (Kotlin_getDestroyRuntimeMode()) {
|
||||
case DESTROY_RUNTIME_LEGACY:
|
||||
compareAndSwap(&globalRuntimeStatus, kGlobalRuntimeUninitialized, kGlobalRuntimeRunning);
|
||||
result->memoryState = InitMemory(false); // The argument will be ignored for legacy DestroyRuntimeMode
|
||||
result->worker = WorkerInit(true);
|
||||
firstRuntime = atomicAdd(&aliveRuntimesCount, 1) == 1;
|
||||
break;
|
||||
case DESTROY_RUNTIME_ON_SHUTDOWN:
|
||||
// First update `aliveRuntimesCount` and then update `globalRuntimeStatus`, for synchronization with
|
||||
// runtime shutdown, which does it the other way around.
|
||||
atomicAdd(&aliveRuntimesCount, 1);
|
||||
auto lastStatus = compareAndSwap(&globalRuntimeStatus, kGlobalRuntimeUninitialized, kGlobalRuntimeRunning);
|
||||
if (Kotlin_forceCheckedShutdown()) {
|
||||
RuntimeAssert(lastStatus != kGlobalRuntimeShutdown, "Kotlin runtime was shut down. Cannot create new runtimes.");
|
||||
}
|
||||
firstRuntime = lastStatus == kGlobalRuntimeUninitialized;
|
||||
result->memoryState = InitMemory(firstRuntime);
|
||||
result->worker = WorkerInit(true);
|
||||
}
|
||||
|
||||
result->memoryState = InitMemory();
|
||||
result->worker = WorkerInit(true);
|
||||
bool firstRuntime = atomicAdd(&aliveRuntimesCount, 1) == 1;
|
||||
// Keep global variables in state as well.
|
||||
if (firstRuntime) {
|
||||
konan::consoleInit();
|
||||
@@ -110,25 +134,33 @@ RuntimeState* initRuntime() {
|
||||
return result;
|
||||
}
|
||||
|
||||
void deinitRuntime(RuntimeState* state) {
|
||||
void deinitRuntime(RuntimeState* state, bool destroyRuntime) {
|
||||
RuntimeAssert(state->status == RuntimeStatus::kRunning, "Runtime must be in the running state");
|
||||
state->status = RuntimeStatus::kDestroying;
|
||||
// This may be called after TLS is zeroed out, so ::memoryState in Memory cannot be trusted.
|
||||
RestoreMemory(state->memoryState);
|
||||
bool lastRuntime = atomicAdd(&aliveRuntimesCount, -1) == 0;
|
||||
switch (Kotlin_getDestroyRuntimeMode()) {
|
||||
case DESTROY_RUNTIME_LEGACY:
|
||||
destroyRuntime = lastRuntime;
|
||||
break;
|
||||
case DESTROY_RUNTIME_ON_SHUTDOWN:
|
||||
// Nothing to do.
|
||||
break;
|
||||
}
|
||||
InitOrDeinitGlobalVariables(DEINIT_THREAD_LOCAL_GLOBALS, state->memoryState);
|
||||
if (lastRuntime)
|
||||
if (destroyRuntime)
|
||||
InitOrDeinitGlobalVariables(DEINIT_GLOBALS, state->memoryState);
|
||||
auto workerId = GetWorkerId(state->worker);
|
||||
WorkerDeinit(state->worker);
|
||||
DeinitMemory(state->memoryState);
|
||||
DeinitMemory(state->memoryState, destroyRuntime);
|
||||
konanDestructInstance(state);
|
||||
WorkerDestroyThreadDataIfNeeded(workerId);
|
||||
}
|
||||
|
||||
void Kotlin_deinitRuntimeCallback(void* argument) {
|
||||
auto* state = reinterpret_cast<RuntimeState*>(argument);
|
||||
deinitRuntime(state);
|
||||
deinitRuntime(state, false);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -155,17 +187,31 @@ void Kotlin_initRuntimeIfNeeded() {
|
||||
|
||||
void Kotlin_deinitRuntimeIfNeeded() {
|
||||
if (isValidRuntime()) {
|
||||
deinitRuntime(::runtimeState);
|
||||
deinitRuntime(::runtimeState, false);
|
||||
::runtimeState = kInvalidRuntime;
|
||||
}
|
||||
}
|
||||
|
||||
// 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");
|
||||
|
||||
bool needsFullShutdown = false;
|
||||
switch (Kotlin_getDestroyRuntimeMode()) {
|
||||
case DESTROY_RUNTIME_LEGACY:
|
||||
needsFullShutdown = true;
|
||||
break;
|
||||
case DESTROY_RUNTIME_ON_SHUTDOWN:
|
||||
needsFullShutdown = Kotlin_forceCheckedShutdown() || Kotlin_memoryLeakCheckerEnabled() || Kotlin_cleanersLeakCheckerEnabled();
|
||||
break;
|
||||
}
|
||||
if (!needsFullShutdown) {
|
||||
auto lastStatus = compareAndSwap(&globalRuntimeStatus, kGlobalRuntimeRunning, kGlobalRuntimeShutdown);
|
||||
RuntimeAssert(lastStatus == kGlobalRuntimeRunning, "Invalid runtime status for shutdown");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Kotlin_cleanersLeakCheckerEnabled()) {
|
||||
// Make sure to collect any lingering cleaners.
|
||||
PerformFullGC(runtime->memoryState);
|
||||
@@ -178,13 +224,23 @@ void Kotlin_shutdownRuntime() {
|
||||
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()) {
|
||||
// TODO: When legacy mode is gone, this `if` will become unnecessary.
|
||||
if (Kotlin_forceCheckedShutdown() || Kotlin_memoryLeakCheckerEnabled() || Kotlin_cleanersLeakCheckerEnabled()) {
|
||||
// First make sure workers are gone.
|
||||
WaitNativeWorkersTermination();
|
||||
|
||||
if (Kotlin_forceCheckedShutdown()) {
|
||||
// Now check for existence of any other runtimes.
|
||||
auto otherRuntimesCount = atomicGet(&aliveRuntimesCount) - 1;
|
||||
RuntimeAssert(otherRuntimesCount >= 0, "Cannot be negative");
|
||||
if (otherRuntimesCount > 0) {
|
||||
konan::consoleErrorf("Cannot run checkers when there are %d alive runtimes at the shutdown", otherRuntimesCount);
|
||||
konan::abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinitRuntime(runtime);
|
||||
deinitRuntime(runtime, true);
|
||||
::runtimeState = kInvalidRuntime;
|
||||
}
|
||||
|
||||
@@ -280,4 +336,23 @@ void Konan_Platform_setCleanersLeakChecker(KBoolean value) {
|
||||
g_checkLeakedCleaners = value;
|
||||
}
|
||||
|
||||
bool Kotlin_forceCheckedShutdown() {
|
||||
return g_forceCheckedShutdown;
|
||||
}
|
||||
|
||||
KBoolean Kotlin_Debugging_getForceCheckedShutdown() {
|
||||
return g_forceCheckedShutdown;
|
||||
}
|
||||
|
||||
void Kotlin_Debugging_setForceCheckedShutdown(KBoolean value) {
|
||||
switch (Kotlin_getDestroyRuntimeMode()) {
|
||||
case DESTROY_RUNTIME_LEGACY:
|
||||
// Only applicable to ON_SHUTDOWN modes.
|
||||
return;
|
||||
case DESTROY_RUNTIME_ON_SHUTDOWN:
|
||||
break;
|
||||
}
|
||||
g_forceCheckedShutdown = value;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@@ -25,6 +25,14 @@ struct InitNode;
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Must match DestroyRuntimeMode in DestroyRuntimeMode.kt
|
||||
enum DestroyRuntimeMode {
|
||||
DESTROY_RUNTIME_LEGACY = 0,
|
||||
DESTROY_RUNTIME_ON_SHUTDOWN = 1,
|
||||
};
|
||||
|
||||
DestroyRuntimeMode Kotlin_getDestroyRuntimeMode();
|
||||
|
||||
void Kotlin_initRuntimeIfNeeded();
|
||||
void Kotlin_deinitRuntimeIfNeeded();
|
||||
|
||||
@@ -41,6 +49,8 @@ bool Kotlin_memoryLeakCheckerEnabled();
|
||||
|
||||
bool Kotlin_cleanersLeakCheckerEnabled();
|
||||
|
||||
bool Kotlin_forceCheckedShutdown();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
package kotlin.native.internal
|
||||
|
||||
/*
|
||||
* Internal utilities for debugging K/N compiler and runtime.
|
||||
*/
|
||||
public object Debugging {
|
||||
public var forceCheckedShutdown: Boolean
|
||||
get() = Debugging_getForceCheckedShutdown()
|
||||
set(value) = Debugging_setForceCheckedShutdown(value)
|
||||
}
|
||||
|
||||
@SymbolName("Kotlin_Debugging_getForceCheckedShutdown")
|
||||
private external fun Debugging_getForceCheckedShutdown(): Boolean
|
||||
|
||||
@SymbolName("Kotlin_Debugging_setForceCheckedShutdown")
|
||||
private external fun Debugging_setForceCheckedShutdown(value: Boolean): Unit
|
||||
@@ -47,11 +47,11 @@ static void destroyMetaObject(TypeInfo** location) {
|
||||
|
||||
extern "C" {
|
||||
|
||||
MemoryState* InitMemory() {
|
||||
MemoryState* InitMemory(bool firstRuntime) {
|
||||
RuntimeCheck(false, "Unimplemented");
|
||||
}
|
||||
|
||||
void DeinitMemory(MemoryState*) {
|
||||
void DeinitMemory(MemoryState*, bool destroyRuntime) {
|
||||
RuntimeCheck(false, "Unimplemented");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user