[K/N] Make GC.collect wait for finalizers being run

^KT-50713

Merge-request: KT-MR-5404
Merged-by: Alexander Shabalin <Alexander.Shabalin@jetbrains.com>
This commit is contained in:
Alexander Shabalin
2022-01-11 11:39:22 +00:00
committed by Space
parent c80d9d8dfb
commit 65fdfffeb4
15 changed files with 112 additions and 19 deletions
@@ -75,10 +75,6 @@ void gc::ConcurrentMarkAndSweep::ThreadData::ScheduleAndWaitFullGCWithFinalizers
gc_.state_.waitEpochFinalized(scheduled_epoch);
}
void gc::ConcurrentMarkAndSweep::ThreadData::StopFinalizerThreadForTests() noexcept {
gc_.finalizerProcessor_->StopFinalizerThread();
}
void gc::ConcurrentMarkAndSweep::ThreadData::OnOOM(size_t size) noexcept {
RuntimeLogDebug({kTagGC}, "Attempt to GC on OOM at size=%zu", size);
ScheduleAndWaitFullGC();
@@ -110,6 +106,21 @@ gc::ConcurrentMarkAndSweep::~ConcurrentMarkAndSweep() {
gcThread_.join();
}
void gc::ConcurrentMarkAndSweep::StartFinalizerThreadIfNeeded() noexcept {
NativeOrUnregisteredThreadGuard guard(true);
finalizerProcessor_->StartFinalizerThreadIfNone();
finalizerProcessor_->WaitFinalizerThreadInitialized();
}
void gc::ConcurrentMarkAndSweep::StopFinalizerThreadIfRunning() noexcept {
NativeOrUnregisteredThreadGuard guard(true);
finalizerProcessor_->StopFinalizerThread();
}
bool gc::ConcurrentMarkAndSweep::FinalizersThreadIsRunning() noexcept {
return finalizerProcessor_->IsRunning();
}
bool gc::ConcurrentMarkAndSweep::PerformFullGC(int64_t epoch) noexcept {
RuntimeLogDebug({kTagGC}, "Attempt to suspend threads by thread %d", konan::currentThreadId());
auto timeStartUs = konan::getTimeMicros();
@@ -57,7 +57,6 @@ public:
void ScheduleAndWaitFullGC() noexcept;
void ScheduleAndWaitFullGCWithFinalizers() noexcept;
void StopFinalizerThreadForTests() noexcept;
void OnOOM(size_t size) noexcept;
@@ -73,6 +72,10 @@ public:
ConcurrentMarkAndSweep(mm::ObjectFactory<ConcurrentMarkAndSweep>& objectFactory, GCScheduler& scheduler) noexcept;
~ConcurrentMarkAndSweep();
void StartFinalizerThreadIfNeeded() noexcept;
void StopFinalizerThreadIfRunning() noexcept;
bool FinalizersThreadIsRunning() noexcept;
private:
// Returns `true` if GC has happened, and `false` if not (because someone else has suspended the threads).
bool PerformFullGC(int64_t epoch) noexcept;
@@ -340,7 +340,6 @@ TEST_F(ConcurrentMarkAndSweepTest, FreeObjectsWithFinalizers) {
EXPECT_CALL(finalizerHook(), Call(object1.header()));
EXPECT_CALL(finalizerHook(), Call(object2.header()));
threadData.gc().ScheduleAndWaitFullGCWithFinalizers();
threadData.gc().impl().gc().StopFinalizerThreadForTests();
EXPECT_THAT(Alive(threadData), testing::UnorderedElementsAre());
});
@@ -360,7 +359,6 @@ TEST_F(ConcurrentMarkAndSweepTest, FreeObjectWithFreeWeak) {
ASSERT_THAT(weak1->referred, object1.header());
threadData.gc().ScheduleAndWaitFullGCWithFinalizers();
threadData.gc().impl().gc().StopFinalizerThreadForTests();
EXPECT_THAT(Alive(threadData), testing::UnorderedElementsAre());
});
@@ -511,7 +509,6 @@ TEST_F(ConcurrentMarkAndSweepTest, ObjectsWithCyclesAndFinalizers) {
EXPECT_CALL(finalizerHook(), Call(object5.header()));
EXPECT_CALL(finalizerHook(), Call(object6.header()));
threadData.gc().ScheduleAndWaitFullGCWithFinalizers();
threadData.gc().impl().gc().StopFinalizerThreadForTests();
EXPECT_THAT(
Alive(threadData),
@@ -14,6 +14,11 @@ void kotlin::gc::FinalizerProcessor::StartFinalizerThreadIfNone() noexcept {
if (finalizerThread_.joinable()) return;
finalizerThread_ = std::thread([this] {
Kotlin_initRuntimeIfNeeded();
{
std::unique_lock guard(initializedMutex_);
initialized_ = true;
}
initializedCondVar_.notify_all();
int64_t finalizersEpoch = 0;
while (true) {
std::unique_lock lock(finalizerQueueMutex_);
@@ -34,6 +39,11 @@ void kotlin::gc::FinalizerProcessor::StartFinalizerThreadIfNone() noexcept {
}
epochDoneCallback_(finalizersEpoch);
}
{
std::unique_lock guard(initializedMutex_);
initialized_ = false;
}
initializedCondVar_.notify_all();
});
}
@@ -69,6 +79,11 @@ bool kotlin::gc::FinalizerProcessor::IsRunning() noexcept {
return finalizerThread_.joinable();
}
void kotlin::gc::FinalizerProcessor::WaitFinalizerThreadInitialized() noexcept {
std::unique_lock guard(initializedMutex_);
initializedCondVar_.wait(guard, [this] { return initialized_; });
}
kotlin::gc::FinalizerProcessor::~FinalizerProcessor() {
StopFinalizerThread();
}
}
@@ -10,6 +10,7 @@
#include "GCState.hpp"
namespace kotlin::gc {
class FinalizerProcessor : Pinned {
public:
using Queue = typename kotlin::mm::ObjectFactory<ConcurrentMarkAndSweep>::FinalizerQueue;
@@ -19,9 +20,11 @@ public:
void ScheduleTasks(Queue&& tasks, int64_t epoch) noexcept;
void StopFinalizerThread() noexcept;
bool IsRunning() noexcept;
~FinalizerProcessor();
private:
void StartFinalizerThreadIfNone() noexcept;
void WaitFinalizerThreadInitialized() noexcept;
~FinalizerProcessor();
private:
std::thread finalizerThread_;
Queue finalizerQueue_;
std::condition_variable finalizerQueueCondVar_;
@@ -30,5 +33,10 @@ private:
int64_t finalizerQueueEpoch_ = 0;
bool shutdownFlag_ = false;
bool newTasksAllowed_ = true;
std::mutex initializedMutex_;
std::condition_variable initializedCondVar_;
bool initialized_ = false;
};
}
} // namespace kotlin::gc
@@ -73,5 +73,18 @@ gc::GCSchedulerConfig& gc::GC::gcSchedulerConfig() noexcept {
}
void gc::GC::ClearForTests() noexcept {
impl_->gc().StopFinalizerThreadIfRunning();
impl_->objectFactory().ClearForTests();
}
void gc::GC::StartFinalizerThreadIfNeeded() noexcept {
impl_->gc().StartFinalizerThreadIfNeeded();
}
void gc::GC::StopFinalizerThreadIfRunning() noexcept {
impl_->gc().StopFinalizerThreadIfRunning();
}
bool gc::GC::FinalizersThreadIsRunning() noexcept {
return impl_->gc().FinalizersThreadIsRunning();
}
@@ -60,6 +60,10 @@ public:
void ClearForTests() noexcept;
void StartFinalizerThreadIfNeeded() noexcept;
void StopFinalizerThreadIfRunning() noexcept;
bool FinalizersThreadIsRunning() noexcept;
private:
KStdUniquePtr<Impl> impl_;
};
@@ -65,3 +65,11 @@ gc::GCSchedulerConfig& gc::GC::gcSchedulerConfig() noexcept {
void gc::GC::ClearForTests() noexcept {
impl_->objectFactory().ClearForTests();
}
void gc::GC::StartFinalizerThreadIfNeeded() noexcept {}
void gc::GC::StopFinalizerThreadIfRunning() noexcept {}
bool gc::GC::FinalizersThreadIsRunning() noexcept {
return false;
}
@@ -55,7 +55,6 @@ public:
~NoOpGC() = default;
GCScheduler& scheduler() noexcept { return scheduler_; }
void StopFinalizerThreadForTests() noexcept {}
private:
GCScheduler scheduler_;
@@ -77,3 +77,11 @@ gc::GCSchedulerConfig& gc::GC::gcSchedulerConfig() noexcept {
void gc::GC::ClearForTests() noexcept {
impl_->objectFactory().ClearForTests();
}
void gc::GC::StartFinalizerThreadIfNeeded() noexcept {}
void gc::GC::StopFinalizerThreadIfRunning() noexcept {}
bool gc::GC::FinalizersThreadIsRunning() noexcept {
return false;
}
@@ -75,7 +75,6 @@ public:
SameThreadMarkAndSweep(mm::ObjectFactory<SameThreadMarkAndSweep>& objectFactory, GCScheduler& gcScheduler) noexcept;
~SameThreadMarkAndSweep() = default;
void StopFinalizerThreadForTests() noexcept {}
private:
// Returns `true` if GC has happened, and `false` if not (because someone else has suspended the threads).
@@ -3803,3 +3803,9 @@ ALWAYS_INLINE kotlin::CalledFromNativeGuard::CalledFromNativeGuard(bool reentran
}
const bool kotlin::kSupportsMultipleMutators = true;
void kotlin::StartFinalizerThreadIfNeeded() noexcept {}
bool kotlin::FinalizersThreadIsRunning() noexcept {
return false;
}
@@ -520,6 +520,9 @@ private:
extern const bool kSupportsMultipleMutators;
void StartFinalizerThreadIfNeeded() noexcept;
bool FinalizersThreadIsRunning() noexcept;
} // namespace kotlin
#endif // RUNTIME_MEMORY_H
+11 -1
View File
@@ -243,6 +243,10 @@ void Kotlin_shutdownRuntime() {
return;
}
// If we're going to need finalizers for the full shutdown, we need to start the thread before
// new runtimes are disallowed.
kotlin::StartFinalizerThreadIfNeeded();
if (Kotlin_cleanersLeakCheckerEnabled()) {
// Make sure to collect any lingering cleaners.
PerformFullGC(runtime->memoryState);
@@ -262,8 +266,14 @@ void Kotlin_shutdownRuntime() {
// First make sure workers are gone.
WaitNativeWorkersTermination();
// Allow the current runtime.
int knownRuntimes = 1;
if (kotlin::FinalizersThreadIsRunning()) {
++knownRuntimes;
}
// Now check for existence of any other runtimes.
auto otherRuntimesCount = atomicGet(&aliveRuntimesCount) - 1;
auto otherRuntimesCount = atomicGet(&aliveRuntimesCount) - knownRuntimes;
RuntimeAssert(otherRuntimesCount >= 0, "Cannot be negative");
if (Kotlin_forceCheckedShutdown()) {
if (otherRuntimesCount > 0) {
+13 -4
View File
@@ -111,8 +111,9 @@ extern "C" void DeinitMemory(MemoryState* state, bool destroyRuntime) {
auto* node = mm::FromMemoryState(state);
if (destroyRuntime) {
ThreadStateGuard guard(state, ThreadState::kRunnable);
node->Get()->gc().ScheduleAndWaitFullGC();
// TODO: Also make sure that finalizers are run.
node->Get()->gc().ScheduleAndWaitFullGCWithFinalizers();
// TODO: Why not just destruct `GC` object and its thread data counterpart entirely?
mm::GlobalData::Instance().gc().StopFinalizerThreadIfRunning();
}
mm::ThreadRegistry::Instance().Unregister(node);
if (destroyRuntime) {
@@ -293,7 +294,7 @@ extern "C" RUNTIME_NOTHROW void GC_CollectorCallback(void* worker) {
extern "C" void Kotlin_native_internal_GC_collect(ObjHeader*) {
auto* threadData = mm::ThreadRegistry::Instance().CurrentThreadData();
threadData->gc().ScheduleAndWaitFullGC();
threadData->gc().ScheduleAndWaitFullGCWithFinalizers();
}
extern "C" void Kotlin_native_internal_GC_collectCyclic(ObjHeader*) {
@@ -402,7 +403,7 @@ extern "C" void Kotlin_Any_share(ObjHeader* thiz) {
}
extern "C" RUNTIME_NOTHROW void PerformFullGC(MemoryState* memory) {
memory->GetThreadData()->gc().ScheduleAndWaitFullGC();
memory->GetThreadData()->gc().ScheduleAndWaitFullGCWithFinalizers();
}
extern "C" bool TryAddHeapRef(const ObjHeader* object) {
@@ -572,3 +573,11 @@ ALWAYS_INLINE kotlin::CalledFromNativeGuard::CalledFromNativeGuard(bool reentran
}
const bool kotlin::kSupportsMultipleMutators = kotlin::gc::kSupportsMultipleMutators;
void kotlin::StartFinalizerThreadIfNeeded() noexcept {
mm::GlobalData::Instance().gc().StartFinalizerThreadIfNeeded();
}
bool kotlin::FinalizersThreadIsRunning() noexcept {
return mm::GlobalData::Instance().gc().FinalizersThreadIsRunning();
}