[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:
committed by
Space Team
parent
a99db60117
commit
4eac193463
@@ -71,14 +71,6 @@ bool gc::ConcurrentMarkAndSweep::ThreadData::tryLockRootSet() {
|
|||||||
return locked;
|
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() {
|
void gc::ConcurrentMarkAndSweep::ThreadData::publish() {
|
||||||
threadData_.Publish();
|
threadData_.Publish();
|
||||||
published_.store(true, std::memory_order_release);
|
published_.store(true, std::memory_order_release);
|
||||||
@@ -90,7 +82,6 @@ bool gc::ConcurrentMarkAndSweep::ThreadData::published() const {
|
|||||||
|
|
||||||
void gc::ConcurrentMarkAndSweep::ThreadData::clearMarkFlags() {
|
void gc::ConcurrentMarkAndSweep::ThreadData::clearMarkFlags() {
|
||||||
published_.store(false, std::memory_order_relaxed);
|
published_.store(false, std::memory_order_relaxed);
|
||||||
cooperative_.store(false, std::memory_order_relaxed);
|
|
||||||
rootSetLocked_.store(false, std::memory_order_release);
|
rootSetLocked_.store(false, std::memory_order_release);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,14 +161,7 @@ void gc::ConcurrentMarkAndSweep::PerformFullGC(int64_t epoch) noexcept {
|
|||||||
markDispatcher_.beginMarkingEpoch(gcHandle);
|
markDispatcher_.beginMarkingEpoch(gcHandle);
|
||||||
GCLogDebug(epoch, "Main GC requested marking in mutators");
|
GCLogDebug(epoch, "Main GC requested marking in mutators");
|
||||||
|
|
||||||
// Request STW
|
stopTheWorld(gcHandle);
|
||||||
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();
|
|
||||||
|
|
||||||
auto& scheduler = gcScheduler_;
|
auto& scheduler = gcScheduler_;
|
||||||
scheduler.onGCStart();
|
scheduler.onGCStart();
|
||||||
@@ -188,26 +172,17 @@ void gc::ConcurrentMarkAndSweep::PerformFullGC(int64_t epoch) noexcept {
|
|||||||
|
|
||||||
markDispatcher_.endMarkingEpoch();
|
markDispatcher_.endMarkingEpoch();
|
||||||
|
|
||||||
mm::WaitForThreadsSuspension();
|
|
||||||
|
|
||||||
if (compiler::concurrentWeakSweep()) {
|
if (compiler::concurrentWeakSweep()) {
|
||||||
EnableWeakRefBarriers(epoch);
|
// Expected to happen inside STW.
|
||||||
|
gc::EnableWeakRefBarriers(epoch);
|
||||||
mm::ResumeThreads();
|
resumeTheWorld(gcHandle);
|
||||||
gcHandle.threadsAreResumed();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gc::processWeaks<DefaultProcessWeaksTraits>(gcHandle, mm::SpecialRefRegistry::instance());
|
gc::processWeaks<DefaultProcessWeaksTraits>(gcHandle, mm::SpecialRefRegistry::instance());
|
||||||
|
|
||||||
if (compiler::concurrentWeakSweep()) {
|
if (compiler::concurrentWeakSweep()) {
|
||||||
bool didSuspend = mm::RequestThreadsSuspension();
|
stopTheWorld(gcHandle);
|
||||||
RuntimeAssert(didSuspend, "Only GC thread can request suspension");
|
gc::DisableWeakRefBarriers();
|
||||||
gcHandle.suspensionRequested();
|
|
||||||
|
|
||||||
mm::WaitForThreadsSuspension();
|
|
||||||
GCLogDebug(gcHandle.getEpoch(), "All threads have paused mutation");
|
|
||||||
gcHandle.threadsAreSuspended();
|
|
||||||
DisableWeakRefBarriers();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO outline as mark_.isolateMarkedHeapAndFinishMark()
|
// TODO outline as mark_.isolateMarkedHeapAndFinishMark()
|
||||||
@@ -233,8 +208,7 @@ void gc::ConcurrentMarkAndSweep::PerformFullGC(int64_t epoch) noexcept {
|
|||||||
checkMarkCorrectness(*objectFactoryIterable);
|
checkMarkCorrectness(*objectFactoryIterable);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
mm::ResumeThreads();
|
resumeTheWorld(gcHandle);
|
||||||
gcHandle.threadsAreResumed();
|
|
||||||
|
|
||||||
#ifndef CUSTOM_ALLOCATOR
|
#ifndef CUSTOM_ALLOCATOR
|
||||||
gc::SweepExtraObjects<DefaultSweepTraits<ObjectFactory>>(gcHandle, *extraObjectFactoryIterable);
|
gc::SweepExtraObjects<DefaultSweepTraits<ObjectFactory>>(gcHandle, *extraObjectFactoryIterable);
|
||||||
|
|||||||
@@ -45,8 +45,6 @@ public:
|
|||||||
BarriersThreadData& barriers() noexcept { return barriers_; }
|
BarriersThreadData& barriers() noexcept { return barriers_; }
|
||||||
|
|
||||||
bool tryLockRootSet();
|
bool tryLockRootSet();
|
||||||
void beginCooperation();
|
|
||||||
bool cooperative() const;
|
|
||||||
void publish();
|
void publish();
|
||||||
bool published() const;
|
bool published() const;
|
||||||
void clearMarkFlags();
|
void clearMarkFlags();
|
||||||
@@ -61,7 +59,6 @@ public:
|
|||||||
|
|
||||||
std::atomic<bool> rootSetLocked_ = false;
|
std::atomic<bool> rootSetLocked_ = false;
|
||||||
std::atomic<bool> published_ = false;
|
std::atomic<bool> published_ = false;
|
||||||
std::atomic<bool> cooperative_ = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef CUSTOM_ALLOCATOR
|
#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() {
|
void gc::mark::ParallelMark::endMarkingEpoch() {
|
||||||
if (!compiler::gcMarkSingleThreaded()) {
|
if (!compiler::gcMarkSingleThreaded()) {
|
||||||
// We must now wait for every worker to finish the Mark procedure:
|
// 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 epoch = gcHandle().getEpoch();
|
||||||
auto parallelWorker = createWorker();
|
auto parallelWorker = createWorker();
|
||||||
if (parallelWorker) {
|
if (parallelWorker) {
|
||||||
auto& gcData = mutatorThread.gc().impl().gc();
|
|
||||||
gcData.beginCooperation();
|
|
||||||
GCLogDebug(epoch, "Mutator thread cooperates in marking");
|
GCLogDebug(epoch, "Mutator thread cooperates in marking");
|
||||||
|
|
||||||
tryCollectRootSet(mutatorThread, *parallelWorker);
|
tryCollectRootSet(mutatorThread, *parallelWorker);
|
||||||
|
|||||||
@@ -110,7 +110,6 @@ public:
|
|||||||
ParallelMark(bool mutatorsCooperate);
|
ParallelMark(bool mutatorsCooperate);
|
||||||
|
|
||||||
void beginMarkingEpoch(gc::GCHandle gcHandle);
|
void beginMarkingEpoch(gc::GCHandle gcHandle);
|
||||||
void waitForThreadsPauseMutation() noexcept;
|
|
||||||
void endMarkingEpoch();
|
void endMarkingEpoch();
|
||||||
|
|
||||||
/** To be run by a single "main" GC thread during STW. */
|
/** To be run by a single "main" GC thread during STW. */
|
||||||
|
|||||||
@@ -4,3 +4,17 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "MarkAndSweepUtils.hpp"
|
#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); }
|
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 gc
|
||||||
} // namespace kotlin
|
} // namespace kotlin
|
||||||
|
|
||||||
|
|||||||
@@ -71,13 +71,8 @@ bool gc::SameThreadMarkAndSweep::FinalizersThreadIsRunning() noexcept {
|
|||||||
|
|
||||||
void gc::SameThreadMarkAndSweep::PerformFullGC(int64_t epoch) noexcept {
|
void gc::SameThreadMarkAndSweep::PerformFullGC(int64_t epoch) noexcept {
|
||||||
auto gcHandle = GCHandle::create(epoch);
|
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");
|
stopTheWorld(gcHandle);
|
||||||
mm::WaitForThreadsSuspension();
|
|
||||||
gcHandle.threadsAreSuspended();
|
|
||||||
|
|
||||||
auto& scheduler = gcScheduler_;
|
auto& scheduler = gcScheduler_;
|
||||||
scheduler.onGCStart();
|
scheduler.onGCStart();
|
||||||
@@ -124,8 +119,8 @@ void gc::SameThreadMarkAndSweep::PerformFullGC(int64_t epoch) noexcept {
|
|||||||
|
|
||||||
scheduler.onGCFinish(epoch, allocatedBytes());
|
scheduler.onGCFinish(epoch, allocatedBytes());
|
||||||
|
|
||||||
mm::ResumeThreads();
|
resumeTheWorld(gcHandle);
|
||||||
gcHandle.threadsAreResumed();
|
|
||||||
state_.finish(epoch);
|
state_.finish(epoch);
|
||||||
gcHandle.finalizersScheduled(finalizerQueue.size());
|
gcHandle.finalizersScheduled(finalizerQueue.size());
|
||||||
gcHandle.finished();
|
gcHandle.finished();
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ void gcScheduler::internal::MutatorAssists::ThreadData::safePoint() noexcept {
|
|||||||
Epoch epoch = owner_.assistsEpoch_.load(std::memory_order_acquire);
|
Epoch epoch = owner_.assistsEpoch_.load(std::memory_order_acquire);
|
||||||
auto noNeedToWait = [this, epoch] { return owner_.completedEpoch_.load(std::memory_order_acquire) >= epoch; };
|
auto noNeedToWait = [this, epoch] { return owner_.completedEpoch_.load(std::memory_order_acquire) >= epoch; };
|
||||||
if (noNeedToWait()) return;
|
if (noNeedToWait()) return;
|
||||||
auto prevState = thread_.suspensionData().setStateNoSafePoint(ThreadState::kNative);
|
auto pauseHandle = thread_.suspensionData().pauseMutationInScope("assisting GC");
|
||||||
RuntimeAssert(prevState == ThreadState::kRunnable, "Expected runnable state");
|
|
||||||
startedWaiting_.store(epoch * 2, std::memory_order_release);
|
startedWaiting_.store(epoch * 2, std::memory_order_release);
|
||||||
{
|
{
|
||||||
std::unique_lock guard(owner_.m_);
|
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);
|
startedWaiting_.store(epoch * 2 + 1, std::memory_order_release);
|
||||||
// Not doing a safe point. We're a safe point.
|
// 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 {
|
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* kTagGC = "gc";
|
||||||
inline constexpr const char* kTagMM = "mm";
|
inline constexpr const char* kTagMM = "mm";
|
||||||
inline constexpr const char* kTagTLS = "tls";
|
inline constexpr const char* kTagTLS = "tls";
|
||||||
|
inline constexpr const char* kTagPause = "pause";
|
||||||
|
|
||||||
} // namespace kotlin
|
} // namespace kotlin
|
||||||
|
|
||||||
|
|||||||
@@ -7,14 +7,11 @@
|
|||||||
#include "ThreadSuspension.hpp"
|
#include "ThreadSuspension.hpp"
|
||||||
|
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <thread>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
#include "CallsChecker.hpp"
|
#include "CallsChecker.hpp"
|
||||||
#include "Logging.hpp"
|
#include "Logging.hpp"
|
||||||
#include "Porting.h"
|
#include "Porting.h"
|
||||||
#include "SafePoint.hpp"
|
#include "SafePoint.hpp"
|
||||||
#include "StackTrace.hpp"
|
|
||||||
|
|
||||||
using namespace kotlin;
|
using namespace kotlin;
|
||||||
|
|
||||||
@@ -26,12 +23,29 @@ namespace {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool kotlin::mm::isSuspendedOrNative(kotlin::mm::ThreadData& thread) noexcept {
|
std::atomic<bool> kotlin::mm::internal::gSuspensionRequested = false;
|
||||||
auto& suspensionData = thread.suspensionData();
|
|
||||||
return suspensionData.suspended() || suspensionData.state() == kotlin::ThreadState::kNative;
|
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 {
|
kotlin::ThreadState kotlin::mm::ThreadSuspensionData::setState(kotlin::ThreadState newState) noexcept {
|
||||||
ThreadState oldState = state_.exchange(newState);
|
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 {
|
NO_EXTERNAL_CALLS_CHECK void kotlin::mm::ThreadSuspensionData::suspendIfRequested() noexcept {
|
||||||
if (IsThreadSuspensionRequested()) {
|
if (IsThreadSuspensionRequested()) {
|
||||||
auto suspendStartMs = konan::getTimeMicros();
|
auto pauseHandle = pauseMutationInScope("stop the world");
|
||||||
|
|
||||||
threadData_.gc().OnSuspendForGC();
|
threadData_.gc().OnSuspendForGC();
|
||||||
std::unique_lock lock(gSuspensionMutex);
|
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(); });
|
gSuspensionCondVar.wait(lock, []() { return !IsThreadSuspensionRequested(); });
|
||||||
auto suspendEndMs = konan::getTimeMicros();
|
|
||||||
RuntimeLogDebug({kTagGC, kTagMM}, "Resuming thread %d after %" PRIu64 " microseconds of suspension",
|
// Must return to running state under the lock.
|
||||||
threadId, suspendEndMs - suspendStartMs);
|
pauseHandle.resume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ALWAYS_INLINE mm::ThreadSuspensionData::MutatorPauseHandle mm::ThreadSuspensionData::pauseMutationInScope(const char* reason) noexcept {
|
||||||
|
return MutatorPauseHandle(reason, threadData_);
|
||||||
|
}
|
||||||
|
|
||||||
bool kotlin::mm::RequestThreadsSuspension() noexcept {
|
bool kotlin::mm::RequestThreadsSuspension() noexcept {
|
||||||
CallsCheckerIgnoreGuard guard;
|
CallsCheckerIgnoreGuard guard;
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,21 @@ inline bool IsThreadSuspensionRequested() noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ThreadSuspensionData : private Pinned {
|
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:
|
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;
|
~ThreadSuspensionData() = default;
|
||||||
|
|
||||||
@@ -38,22 +51,29 @@ public:
|
|||||||
ThreadState setState(ThreadState newState) noexcept;
|
ThreadState setState(ThreadState newState) noexcept;
|
||||||
ThreadState setStateNoSafePoint(ThreadState newState) noexcept { return state_.exchange(newState, std::memory_order_acq_rel); }
|
ThreadState setStateNoSafePoint(ThreadState newState) noexcept { return state_.exchange(newState, std::memory_order_acq_rel); }
|
||||||
|
|
||||||
bool suspended() noexcept { return suspended_; }
|
bool suspendedOrNative() noexcept { return state() == kotlin::ThreadState::kNative; }
|
||||||
bool suspendedOrNative() noexcept { return suspended() || state() == kotlin::ThreadState::kNative; }
|
|
||||||
|
|
||||||
void suspendIfRequested() noexcept;
|
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:
|
private:
|
||||||
std::atomic<ThreadState> state_;
|
std::atomic<ThreadState> state_;
|
||||||
mm::ThreadData& threadData_;
|
mm::ThreadData& threadData_;
|
||||||
std::atomic<bool> suspended_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bool RequestThreadsSuspension() noexcept;
|
bool RequestThreadsSuspension() noexcept;
|
||||||
void WaitForThreadsSuspension() 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.
|
* 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
|
* 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() {
|
std_support::vector<bool> collectSuspended() {
|
||||||
return collectFromThreadData<bool>(
|
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) {
|
void reportProgress(size_t currentIteration, size_t totalIterations) {
|
||||||
@@ -116,9 +116,9 @@ TEST_F(ThreadSuspensionTest, SimpleStartStop) {
|
|||||||
while(!shouldStop) {
|
while(!shouldStop) {
|
||||||
waitUntilCanStart(i);
|
waitUntilCanStart(i);
|
||||||
|
|
||||||
EXPECT_FALSE(suspensionData.suspended());
|
EXPECT_FALSE(suspensionData.suspendedOrNative());
|
||||||
suspensionData.suspendIfRequested();
|
suspensionData.suspendIfRequested();
|
||||||
EXPECT_FALSE(suspensionData.suspended());
|
EXPECT_FALSE(suspensionData.suspendedOrNative());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -201,10 +201,10 @@ TEST_F(ThreadSuspensionTest, ConcurrentSuspend) {
|
|||||||
successCount++;
|
successCount++;
|
||||||
auto allThreadData = collectThreadData();
|
auto allThreadData = collectThreadData();
|
||||||
auto isCurrentOrSuspended = [currentThreadData](mm::ThreadData* data) {
|
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_THAT(allThreadData, testing::Each(testing::Truly(isCurrentOrSuspended)));
|
||||||
EXPECT_FALSE(currentThreadData->suspensionData().suspended());
|
EXPECT_FALSE(currentThreadData->suspensionData().suspendedOrNative());
|
||||||
mm::ResumeThreads();
|
mm::ResumeThreads();
|
||||||
} else {
|
} else {
|
||||||
EXPECT_TRUE(mm::IsThreadSuspensionRequested());
|
EXPECT_TRUE(mm::IsThreadSuspensionRequested());
|
||||||
|
|||||||
Reference in New Issue
Block a user