[K/N] Generalize GC-assistance pause logic

Merge-request: KT-MR-11790
Merged-by: Alexey Glushko <aleksei.glushko@jetbrains.com>
This commit is contained in:
Aleksei.Glushko
2023-08-29 19:31:02 +00:00
committed by Space Team
parent a99db60117
commit 4eac193463
12 changed files with 94 additions and 85 deletions
@@ -71,14 +71,6 @@ bool gc::ConcurrentMarkAndSweep::ThreadData::tryLockRootSet() {
return locked;
}
void gc::ConcurrentMarkAndSweep::ThreadData::beginCooperation() {
cooperative_.store(true, std::memory_order_release);
}
bool gc::ConcurrentMarkAndSweep::ThreadData::cooperative() const {
return cooperative_.load(std::memory_order_relaxed);
}
void gc::ConcurrentMarkAndSweep::ThreadData::publish() {
threadData_.Publish();
published_.store(true, std::memory_order_release);
@@ -90,7 +82,6 @@ bool gc::ConcurrentMarkAndSweep::ThreadData::published() const {
void gc::ConcurrentMarkAndSweep::ThreadData::clearMarkFlags() {
published_.store(false, std::memory_order_relaxed);
cooperative_.store(false, std::memory_order_relaxed);
rootSetLocked_.store(false, std::memory_order_release);
}
@@ -170,14 +161,7 @@ void gc::ConcurrentMarkAndSweep::PerformFullGC(int64_t epoch) noexcept {
markDispatcher_.beginMarkingEpoch(gcHandle);
GCLogDebug(epoch, "Main GC requested marking in mutators");
// Request STW
bool didSuspend = mm::RequestThreadsSuspension();
RuntimeAssert(didSuspend, "Only GC thread can request suspension");
gcHandle.suspensionRequested();
markDispatcher_.waitForThreadsPauseMutation();
GCLogDebug(epoch, "All threads have paused mutation");
gcHandle.threadsAreSuspended();
stopTheWorld(gcHandle);
auto& scheduler = gcScheduler_;
scheduler.onGCStart();
@@ -188,26 +172,17 @@ void gc::ConcurrentMarkAndSweep::PerformFullGC(int64_t epoch) noexcept {
markDispatcher_.endMarkingEpoch();
mm::WaitForThreadsSuspension();
if (compiler::concurrentWeakSweep()) {
EnableWeakRefBarriers(epoch);
mm::ResumeThreads();
gcHandle.threadsAreResumed();
// Expected to happen inside STW.
gc::EnableWeakRefBarriers(epoch);
resumeTheWorld(gcHandle);
}
gc::processWeaks<DefaultProcessWeaksTraits>(gcHandle, mm::SpecialRefRegistry::instance());
if (compiler::concurrentWeakSweep()) {
bool didSuspend = mm::RequestThreadsSuspension();
RuntimeAssert(didSuspend, "Only GC thread can request suspension");
gcHandle.suspensionRequested();
mm::WaitForThreadsSuspension();
GCLogDebug(gcHandle.getEpoch(), "All threads have paused mutation");
gcHandle.threadsAreSuspended();
DisableWeakRefBarriers();
stopTheWorld(gcHandle);
gc::DisableWeakRefBarriers();
}
// TODO outline as mark_.isolateMarkedHeapAndFinishMark()
@@ -233,8 +208,7 @@ void gc::ConcurrentMarkAndSweep::PerformFullGC(int64_t epoch) noexcept {
checkMarkCorrectness(*objectFactoryIterable);
#endif
mm::ResumeThreads();
gcHandle.threadsAreResumed();
resumeTheWorld(gcHandle);
#ifndef CUSTOM_ALLOCATOR
gc::SweepExtraObjects<DefaultSweepTraits<ObjectFactory>>(gcHandle, *extraObjectFactoryIterable);
@@ -45,8 +45,6 @@ public:
BarriersThreadData& barriers() noexcept { return barriers_; }
bool tryLockRootSet();
void beginCooperation();
bool cooperative() const;
void publish();
bool published() const;
void clearMarkFlags();
@@ -61,7 +59,6 @@ public:
std::atomic<bool> rootSetLocked_ = false;
std::atomic<bool> published_ = false;
std::atomic<bool> cooperative_ = false;
};
#ifdef CUSTOM_ALLOCATOR
@@ -85,15 +85,6 @@ void gc::mark::ParallelMark::beginMarkingEpoch(gc::GCHandle gcHandle) {
}
}
void gc::mark::ParallelMark::waitForThreadsPauseMutation() noexcept {
RuntimeAssert(!kotlin::mm::IsCurrentThreadRegistered(), "Dispatcher thread must not be registered");
spinWait([this] {
return allMutators([](mm::ThreadData& mut) {
return mm::isSuspendedOrNative(mut) || mut.gc().impl().gc().cooperative();
});
});
}
void gc::mark::ParallelMark::endMarkingEpoch() {
if (!compiler::gcMarkSingleThreaded()) {
// We must now wait for every worker to finish the Mark procedure:
@@ -139,8 +130,6 @@ void gc::mark::ParallelMark::runOnMutator(mm::ThreadData& mutatorThread) {
auto epoch = gcHandle().getEpoch();
auto parallelWorker = createWorker();
if (parallelWorker) {
auto& gcData = mutatorThread.gc().impl().gc();
gcData.beginCooperation();
GCLogDebug(epoch, "Mutator thread cooperates in marking");
tryCollectRootSet(mutatorThread, *parallelWorker);
@@ -110,7 +110,6 @@ public:
ParallelMark(bool mutatorsCooperate);
void beginMarkingEpoch(gc::GCHandle gcHandle);
void waitForThreadsPauseMutation() noexcept;
void endMarkingEpoch();
/** To be run by a single "main" GC thread during STW. */
@@ -4,3 +4,17 @@
*/
#include "MarkAndSweepUtils.hpp"
void kotlin::gc::stopTheWorld(kotlin::gc::GCHandle gcHandle) noexcept {
bool didSuspend = mm::RequestThreadsSuspension();
RuntimeAssert(didSuspend, "Only GC thread can request suspension");
gcHandle.suspensionRequested();
mm::WaitForThreadsSuspension();
gcHandle.threadsAreSuspended();
}
void kotlin::gc::resumeTheWorld(kotlin::gc::GCHandle gcHandle) noexcept {
mm::ResumeThreads();
gcHandle.threadsAreResumed();
}
@@ -254,6 +254,13 @@ struct DefaultProcessWeaksTraits {
static bool IsMarked(ObjHeader* obj) noexcept { return gc::isMarked(obj); }
};
void stopTheWorld(GCHandle gcHandle) noexcept;
void resumeTheWorld(GCHandle gcHandle) noexcept;
[[nodiscard]] inline auto stopTheWorldInScope(GCHandle gcHandle) noexcept {
return ScopeGuard([=]() { stopTheWorld(gcHandle); }, [=]() { resumeTheWorld(gcHandle); });
}
} // namespace gc
} // namespace kotlin
@@ -71,13 +71,8 @@ bool gc::SameThreadMarkAndSweep::FinalizersThreadIsRunning() noexcept {
void gc::SameThreadMarkAndSweep::PerformFullGC(int64_t epoch) noexcept {
auto gcHandle = GCHandle::create(epoch);
bool didSuspend = mm::RequestThreadsSuspension();
RuntimeAssert(didSuspend, "Only GC thread can request suspension");
gcHandle.suspensionRequested();
RuntimeAssert(!kotlin::mm::IsCurrentThreadRegistered(), "GC must run on unregistered thread");
mm::WaitForThreadsSuspension();
gcHandle.threadsAreSuspended();
stopTheWorld(gcHandle);
auto& scheduler = gcScheduler_;
scheduler.onGCStart();
@@ -124,8 +119,8 @@ void gc::SameThreadMarkAndSweep::PerformFullGC(int64_t epoch) noexcept {
scheduler.onGCFinish(epoch, allocatedBytes());
mm::ResumeThreads();
gcHandle.threadsAreResumed();
resumeTheWorld(gcHandle);
state_.finish(epoch);
gcHandle.finalizersScheduled(finalizerQueue.size());
gcHandle.finished();
@@ -16,8 +16,7 @@ void gcScheduler::internal::MutatorAssists::ThreadData::safePoint() noexcept {
Epoch epoch = owner_.assistsEpoch_.load(std::memory_order_acquire);
auto noNeedToWait = [this, epoch] { return owner_.completedEpoch_.load(std::memory_order_acquire) >= epoch; };
if (noNeedToWait()) return;
auto prevState = thread_.suspensionData().setStateNoSafePoint(ThreadState::kNative);
RuntimeAssert(prevState == ThreadState::kRunnable, "Expected runnable state");
auto pauseHandle = thread_.suspensionData().pauseMutationInScope("assisting GC");
startedWaiting_.store(epoch * 2, std::memory_order_release);
{
std::unique_lock guard(owner_.m_);
@@ -27,8 +26,6 @@ void gcScheduler::internal::MutatorAssists::ThreadData::safePoint() noexcept {
}
startedWaiting_.store(epoch * 2 + 1, std::memory_order_release);
// Not doing a safe point. We're a safe point.
prevState = thread_.suspensionData().setStateNoSafePoint(ThreadState::kRunnable);
RuntimeAssert(prevState == ThreadState::kNative, "Expected native state");
}
bool gcScheduler::internal::MutatorAssists::ThreadData::completedEpoch(Epoch epoch) const noexcept {
@@ -89,6 +89,7 @@ void VLog(Level level, std::initializer_list<const char*> tags, const char* form
inline constexpr const char* kTagGC = "gc";
inline constexpr const char* kTagMM = "mm";
inline constexpr const char* kTagTLS = "tls";
inline constexpr const char* kTagPause = "pause";
} // namespace kotlin
@@ -7,14 +7,11 @@
#include "ThreadSuspension.hpp"
#include <condition_variable>
#include <thread>
#include <mutex>
#include "CallsChecker.hpp"
#include "Logging.hpp"
#include "Porting.h"
#include "SafePoint.hpp"
#include "StackTrace.hpp"
using namespace kotlin;
@@ -26,12 +23,29 @@ namespace {
} // namespace
bool kotlin::mm::isSuspendedOrNative(kotlin::mm::ThreadData& thread) noexcept {
auto& suspensionData = thread.suspensionData();
return suspensionData.suspended() || suspensionData.state() == kotlin::ThreadState::kNative;
std::atomic<bool> kotlin::mm::internal::gSuspensionRequested = false;
ALWAYS_INLINE mm::ThreadSuspensionData::MutatorPauseHandle::MutatorPauseHandle(const char* reason, mm::ThreadData& threadData) noexcept
: reason_(reason), threadData_(threadData), pauseStartTimeMicros_(konan::getTimeMicros())
{
auto prevState = threadData_.suspensionData().setStateNoSafePoint(ThreadState::kNative);
// no special reason, fill free to implement pause from native if needed
RuntimeAssert(prevState == ThreadState::kRunnable, "Expected runnable state");
RuntimeLogDebug({kTagPause}, "Suspending mutation (%s)", reason_);
}
std::atomic<bool> kotlin::mm::internal::gSuspensionRequested = false;
ALWAYS_INLINE mm::ThreadSuspensionData::MutatorPauseHandle::~MutatorPauseHandle() noexcept {
if (!resumed) resume();
}
ALWAYS_INLINE void mm::ThreadSuspensionData::MutatorPauseHandle::resume() noexcept {
RuntimeAssert(!resumed, "Must not be resumed yet");
auto prevState = threadData_.suspensionData().setStateNoSafePoint(ThreadState::kRunnable);
RuntimeAssert(prevState == ThreadState::kNative, "Expected native state");
auto pauseTimeMicros = konan::getTimeMicros() - pauseStartTimeMicros_;
RuntimeLogInfo({kTagPause}, "Resuming mutation after %" PRIu64 " microseconds of suspension (%s)", pauseTimeMicros, reason_);
resumed = true;
}
kotlin::ThreadState kotlin::mm::ThreadSuspensionData::setState(kotlin::ThreadState newState) noexcept {
ThreadState oldState = state_.exchange(newState);
@@ -51,19 +65,21 @@ kotlin::ThreadState kotlin::mm::ThreadSuspensionData::setState(kotlin::ThreadSta
NO_EXTERNAL_CALLS_CHECK void kotlin::mm::ThreadSuspensionData::suspendIfRequested() noexcept {
if (IsThreadSuspensionRequested()) {
auto suspendStartMs = konan::getTimeMicros();
auto pauseHandle = pauseMutationInScope("stop the world");
threadData_.gc().OnSuspendForGC();
std::unique_lock lock(gSuspensionMutex);
auto threadId = konan::currentThreadId();
RuntimeLogDebug({kTagGC, kTagMM}, "Suspending thread %d", threadId);
AutoReset scopedAssignSuspended(&suspended_, true);
gSuspensionCondVar.wait(lock, []() { return !IsThreadSuspensionRequested(); });
auto suspendEndMs = konan::getTimeMicros();
RuntimeLogDebug({kTagGC, kTagMM}, "Resuming thread %d after %" PRIu64 " microseconds of suspension",
threadId, suspendEndMs - suspendStartMs);
// Must return to running state under the lock.
pauseHandle.resume();
}
}
ALWAYS_INLINE mm::ThreadSuspensionData::MutatorPauseHandle mm::ThreadSuspensionData::pauseMutationInScope(const char* reason) noexcept {
return MutatorPauseHandle(reason, threadData_);
}
bool kotlin::mm::RequestThreadsSuspension() noexcept {
CallsCheckerIgnoreGuard guard;
@@ -28,8 +28,21 @@ inline bool IsThreadSuspensionRequested() noexcept {
}
class ThreadSuspensionData : private Pinned {
private:
class MutatorPauseHandle : private Pinned {
public:
explicit MutatorPauseHandle(const char* reason, ThreadData& threadData) noexcept;
~MutatorPauseHandle() noexcept;
void resume() noexcept;
private:
const char* reason_;
ThreadData& threadData_;
uint64_t pauseStartTimeMicros_;
bool resumed = false;
};
public:
explicit ThreadSuspensionData(ThreadState initialState, mm::ThreadData& threadData) noexcept : state_(initialState), threadData_(threadData), suspended_(false) {}
explicit ThreadSuspensionData(ThreadState initialState, mm::ThreadData& threadData) noexcept : state_(initialState), threadData_(threadData) {}
~ThreadSuspensionData() = default;
@@ -38,22 +51,29 @@ public:
ThreadState setState(ThreadState newState) noexcept;
ThreadState setStateNoSafePoint(ThreadState newState) noexcept { return state_.exchange(newState, std::memory_order_acq_rel); }
bool suspended() noexcept { return suspended_; }
bool suspendedOrNative() noexcept { return suspended() || state() == kotlin::ThreadState::kNative; }
bool suspendedOrNative() noexcept { return state() == kotlin::ThreadState::kNative; }
void suspendIfRequested() noexcept;
/**
* Signals that the thread would not mutate a heap during a relatively long time.
* For example while waiting for or participating in the GC.
* Effectively sets the thread's state to `kNative`.
*
* The pause is considered completed upon destruction of a returned pause-handle object.
*
* NOTE: the safe point actions will NOT be automatically executed after the pause.
*/
[[nodiscard]] MutatorPauseHandle pauseMutationInScope(const char* reason) noexcept;
private:
std::atomic<ThreadState> state_;
mm::ThreadData& threadData_;
std::atomic<bool> suspended_;
};
bool RequestThreadsSuspension() noexcept;
void WaitForThreadsSuspension() noexcept;
bool isSuspendedOrNative(kotlin::mm::ThreadData& thread) noexcept;
/**
* Suspends all threads registered in ThreadRegistry except threads that are in the Native state.
* Blocks until all such threads are suspended. Threads that are in the Native state on the moment
@@ -52,7 +52,7 @@ std_support::vector<T> collectFromThreadData(F extractFunction) {
std_support::vector<bool> collectSuspended() {
return collectFromThreadData<bool>(
[](mm::ThreadData* threadData) { return threadData->suspensionData().suspended(); });
[](mm::ThreadData* threadData) { return threadData->suspensionData().suspendedOrNative(); });
}
void reportProgress(size_t currentIteration, size_t totalIterations) {
@@ -116,9 +116,9 @@ TEST_F(ThreadSuspensionTest, SimpleStartStop) {
while(!shouldStop) {
waitUntilCanStart(i);
EXPECT_FALSE(suspensionData.suspended());
EXPECT_FALSE(suspensionData.suspendedOrNative());
suspensionData.suspendIfRequested();
EXPECT_FALSE(suspensionData.suspended());
EXPECT_FALSE(suspensionData.suspendedOrNative());
}
});
}
@@ -201,10 +201,10 @@ TEST_F(ThreadSuspensionTest, ConcurrentSuspend) {
successCount++;
auto allThreadData = collectThreadData();
auto isCurrentOrSuspended = [currentThreadData](mm::ThreadData* data) {
return data == currentThreadData || data->suspensionData().suspended();
return data == currentThreadData || data->suspensionData().suspendedOrNative();
};
EXPECT_THAT(allThreadData, testing::Each(testing::Truly(isCurrentOrSuspended)));
EXPECT_FALSE(currentThreadData->suspensionData().suspended());
EXPECT_FALSE(currentThreadData->suspensionData().suspendedOrNative());
mm::ResumeThreads();
} else {
EXPECT_TRUE(mm::IsThreadSuspensionRequested());