Add a switch to destroy runtime only on shutdown (#4482)

This commit is contained in:
Alexander Shabalin
2020-11-13 09:19:10 +03:00
committed by Stanislav Erokhin
parent 37ff2c338e
commit ee508efb23
18 changed files with 254 additions and 37 deletions
@@ -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) {
+2 -2
View File
@@ -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*);
//
+88 -13
View File
@@ -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
+2 -2
View File
@@ -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");
}