[K/N] Prevent unmarked objects from appearing during concurrent weak processing

Merge-request: KT-MR-11614
Merged-by: Alexey Glushko <aleksei.glushko@jetbrains.com>
This commit is contained in:
Aleksei.Glushko
2023-08-26 16:00:12 +00:00
committed by Space Team
parent 3013e3549c
commit 0d04e170b1
21 changed files with 452 additions and 178 deletions
@@ -18,6 +18,7 @@ using namespace kotlin;
namespace { namespace {
std::atomic<ObjHeader* (*)(ObjHeader*)> weakRefBarrier = nullptr; std::atomic<ObjHeader* (*)(ObjHeader*)> weakRefBarrier = nullptr;
std::atomic<int64_t> weakProcessingEpoch = 0;
ObjHeader* weakRefBarrierImpl(ObjHeader* weakReferee) noexcept { ObjHeader* weakRefBarrierImpl(ObjHeader* weakReferee) noexcept {
if (!weakReferee) return nullptr; if (!weakReferee) return nullptr;
@@ -36,47 +37,62 @@ NO_INLINE ObjHeader* weakRefReadSlowPath(std::atomic<ObjHeader*>& weakReferee) n
return barrier ? barrier(weak) : weak; return barrier ? barrier(weak) : weak;
} }
void waitForThreadsToReachCheckpoint() {
// Reset checkpoint on all threads.
for (auto& thr : mm::ThreadRegistry::Instance().LockForIter()) {
thr.gc().impl().gc().barriers().resetCheckpoint();
}
mm::SafePointActivator safePointActivator;
// Wait for all threads to either have passed safepoint or to be in the native state.
// Either of these mean that none of them are inside a weak reference accessing code.
mm::ThreadRegistry::Instance().waitAllThreads([](mm::ThreadData& thread) noexcept {
return thread.gc().impl().gc().barriers().visitedCheckpoint() || thread.suspensionData().suspendedOrNative();
});
}
} // namespace } // namespace
void gc::BarriersThreadData::onCheckpoint() noexcept { void gc::BarriersThreadData::onThreadRegistration() noexcept {
visitedCheckpoint_.store(true, std::memory_order_release); if (weakRefBarrier.load(std::memory_order_acquire) != nullptr) {
startMarkingNewObjects(GCHandle::getByEpoch(weakProcessingEpoch.load(std::memory_order_relaxed)));
}
} }
void gc::BarriersThreadData::resetCheckpoint() noexcept { ALWAYS_INLINE void gc::BarriersThreadData::onSafePoint() noexcept {}
visitedCheckpoint_.store(false, std::memory_order_release);
void gc::BarriersThreadData::startMarkingNewObjects(gc::GCHandle gcHandle) noexcept {
RuntimeAssert(weakRefBarrier.load(std::memory_order_relaxed) != nullptr, "New allocations marking may only be requested by weak ref barriers");
markHandle_ = gcHandle.mark();
} }
bool gc::BarriersThreadData::visitedCheckpoint() const noexcept { void gc::BarriersThreadData::stopMarkingNewObjects() noexcept {
return visitedCheckpoint_.load(std::memory_order_acquire); RuntimeAssert(weakRefBarrier.load(std::memory_order_relaxed) == nullptr, "New allocations marking could only been requested by weak ref barriers");
markHandle_ = std::nullopt;
} }
void gc::EnableWeakRefBarriers() noexcept { bool gc::BarriersThreadData::shouldMarkNewObjects() const noexcept {
return markHandle_.has_value();
}
ALWAYS_INLINE void gc::BarriersThreadData::onAllocation(ObjHeader* allocated) {
if (compiler::concurrentWeakSweep()) {
bool shouldMark = shouldMarkNewObjects();
bool barriersEnabled = weakRefBarrier.load(std::memory_order_relaxed) != nullptr;
RuntimeAssert(shouldMark == barriersEnabled, "New allocations marking must happen with and only with weak ref barriers");
if (shouldMark) {
auto& objectData = objectDataForObject(allocated);
bool wasUnmarked = objectData.tryMark();
RuntimeAssert(wasUnmarked, "No one else could mark this newly allocated object before");
markHandle_->addObject();
}
}
}
void gc::EnableWeakRefBarriers(int64_t epoch) noexcept {
auto mutators = mm::ThreadRegistry::Instance().LockForIter();
weakProcessingEpoch.store(epoch, std::memory_order_relaxed);
weakRefBarrier.store(weakRefBarrierImpl, std::memory_order_seq_cst); weakRefBarrier.store(weakRefBarrierImpl, std::memory_order_seq_cst);
for (auto& mutator: mutators) {
mutator.gc().impl().gc().barriers().startMarkingNewObjects(GCHandle::getByEpoch(epoch));
}
} }
void gc::DisableWeakRefBarriers() noexcept { void gc::DisableWeakRefBarriers() noexcept {
auto mutators = mm::ThreadRegistry::Instance().LockForIter();
weakRefBarrier.store(nullptr, std::memory_order_seq_cst); weakRefBarrier.store(nullptr, std::memory_order_seq_cst);
waitForThreadsToReachCheckpoint(); for (auto& mutator: mutators) {
mutator.gc().impl().gc().barriers().stopMarkingNewObjects();
}
} }
OBJ_GETTER(kotlin::gc::WeakRefRead, std::atomic<ObjHeader*>& weakReferee) noexcept { OBJ_GETTER(gc::WeakRefRead, std::atomic<ObjHeader*>& weakReferee) noexcept {
// TODO: Make this work with GCs that can stop thread at any point.
if (!compiler::concurrentWeakSweep()) { if (!compiler::concurrentWeakSweep()) {
RETURN_OBJ(weakReferee.load(std::memory_order_relaxed)); RETURN_OBJ(weakReferee.load(std::memory_order_relaxed));
} }
@@ -10,23 +10,26 @@
#include "Memory.h" #include "Memory.h"
#include "Utils.hpp" #include "Utils.hpp"
#include "GCStatistics.hpp"
namespace kotlin::gc { namespace kotlin::gc {
class BarriersThreadData : private Pinned { class BarriersThreadData : private Pinned {
public: public:
void onCheckpoint() noexcept; void onThreadRegistration() noexcept;
void resetCheckpoint() noexcept; void onSafePoint() noexcept;
bool visitedCheckpoint() const noexcept;
void startMarkingNewObjects(GCHandle gcHandle) noexcept;
void stopMarkingNewObjects() noexcept;
bool shouldMarkNewObjects() const noexcept;
void onAllocation(ObjHeader* allocated);
private: private:
std::atomic<bool> visitedCheckpoint_ = false; std::optional<GCHandle::GCMarkScope> markHandle_{};
}; };
// Must be called during STW. // Must be called during STW.
void EnableWeakRefBarriers() noexcept; void EnableWeakRefBarriers(int64_t epoch) noexcept;
// Must be called outside STW.
void DisableWeakRefBarriers() noexcept; void DisableWeakRefBarriers() noexcept;
OBJ_GETTER(WeakRefRead, std::atomic<ObjHeader*>& weakReferee) noexcept; OBJ_GETTER(WeakRefRead, std::atomic<ObjHeader*>& weakReferee) noexcept;
@@ -175,21 +175,10 @@ void gc::ConcurrentMarkAndSweep::PerformFullGC(int64_t epoch) noexcept {
RuntimeAssert(didSuspend, "Only GC thread can request suspension"); RuntimeAssert(didSuspend, "Only GC thread can request suspension");
gcHandle.suspensionRequested(); gcHandle.suspensionRequested();
// TODO (WaitForThreadsReadyToMark())
RuntimeAssert(!kotlin::mm::IsCurrentThreadRegistered(), "GC must run on unregistered thread");
markDispatcher_.waitForThreadsPauseMutation(); markDispatcher_.waitForThreadsPauseMutation();
GCLogDebug(epoch, "All threads have paused mutation"); GCLogDebug(epoch, "All threads have paused mutation");
gcHandle.threadsAreSuspended(); gcHandle.threadsAreSuspended();
#ifdef CUSTOM_ALLOCATOR
// This should really be done by each individual thread while waiting
for (auto& thread : kotlin::mm::ThreadRegistry::Instance().LockForIter()) {
thread.gc().impl().alloc().PrepareForGC();
}
heap_.PrepareForGC();
#endif
auto& scheduler = gcScheduler_; auto& scheduler = gcScheduler_;
scheduler.onGCStart(); scheduler.onGCStart();
@@ -201,17 +190,8 @@ void gc::ConcurrentMarkAndSweep::PerformFullGC(int64_t epoch) noexcept {
mm::WaitForThreadsSuspension(); mm::WaitForThreadsSuspension();
#ifndef CUSTOM_ALLOCATOR
// Taking the locks before the pause is completed. So that any destroying thread
// would not publish into the global state at an unexpected time.
std::optional extraObjectFactoryIterable = extraObjectDataFactory_.LockForIter();
std::optional objectFactoryIterable = objectFactory_.LockForIter();
checkMarkCorrectness(*objectFactoryIterable);
#endif
if (compiler::concurrentWeakSweep()) { if (compiler::concurrentWeakSweep()) {
// Expected to happen inside STW. EnableWeakRefBarriers(epoch);
gc::EnableWeakRefBarriers();
mm::ResumeThreads(); mm::ResumeThreads();
gcHandle.threadsAreResumed(); gcHandle.threadsAreResumed();
@@ -220,13 +200,42 @@ void gc::ConcurrentMarkAndSweep::PerformFullGC(int64_t epoch) noexcept {
gc::processWeaks<DefaultProcessWeaksTraits>(gcHandle, mm::SpecialRefRegistry::instance()); gc::processWeaks<DefaultProcessWeaksTraits>(gcHandle, mm::SpecialRefRegistry::instance());
if (compiler::concurrentWeakSweep()) { if (compiler::concurrentWeakSweep()) {
// Expected to happen outside STW. bool didSuspend = mm::RequestThreadsSuspension();
gc::DisableWeakRefBarriers(); RuntimeAssert(didSuspend, "Only GC thread can request suspension");
} else { gcHandle.suspensionRequested();
mm::ResumeThreads();
gcHandle.threadsAreResumed(); mm::WaitForThreadsSuspension();
GCLogDebug(gcHandle.getEpoch(), "All threads have paused mutation");
gcHandle.threadsAreSuspended();
DisableWeakRefBarriers();
} }
// TODO outline as mark_.isolateMarkedHeapAndFinishMark()
// By this point all the alive heap must be marked.
// All the mutations (incl. allocations) after this method will be subject for the next GC.
#ifdef CUSTOM_ALLOCATOR
// This should really be done by each individual thread while waiting
for (auto& thread : kotlin::mm::ThreadRegistry::Instance().LockForIter()) {
thread.gc().impl().alloc().PrepareForGC();
}
auto& heap = mm::GlobalData::Instance().gc().impl().gc().heap();
heap.PrepareForGC();
#else
for (auto& thread : kotlin::mm::ThreadRegistry::Instance().LockForIter()) {
thread.gc().PublishObjectFactory();
}
// Taking the locks before the pause is completed. So that any destroying thread
// would not publish into the global state at an unexpected time.
std::optional objectFactoryIterable = objectFactory_.LockForIter();
std::optional extraObjectFactoryIterable = extraObjectDataFactory_.LockForIter();
checkMarkCorrectness(*objectFactoryIterable);
#endif
mm::ResumeThreads();
gcHandle.threadsAreResumed();
#ifndef CUSTOM_ALLOCATOR #ifndef CUSTOM_ALLOCATOR
gc::SweepExtraObjects<DefaultSweepTraits<ObjectFactory>>(gcHandle, *extraObjectFactoryIterable); gc::SweepExtraObjects<DefaultSweepTraits<ObjectFactory>>(gcHandle, *extraObjectFactoryIterable);
extraObjectFactoryIterable = std::nullopt; extraObjectFactoryIterable = std::nullopt;
@@ -38,7 +38,9 @@ public:
void OnSuspendForGC() noexcept; void OnSuspendForGC() noexcept;
void safePoint() noexcept { barriers_.onCheckpoint(); } void safePoint() noexcept { barriers_.onSafePoint(); }
void onThreadRegistration() noexcept { barriers_.onThreadRegistration(); }
BarriersThreadData& barriers() noexcept { return barriers_; } BarriersThreadData& barriers() noexcept { return barriers_; }
@@ -21,6 +21,7 @@
#include "ObjectTestSupport.hpp" #include "ObjectTestSupport.hpp"
#include "SafePoint.hpp" #include "SafePoint.hpp"
#include "SingleThreadExecutor.hpp" #include "SingleThreadExecutor.hpp"
#include "StableRef.hpp"
#include "TestSupport.hpp" #include "TestSupport.hpp"
#include "ThreadData.hpp" #include "ThreadData.hpp"
#include "WeakRef.hpp" #include "WeakRef.hpp"
@@ -1034,6 +1035,51 @@ TEST_P(ConcurrentMarkAndSweepTest, MultipleMutatorsWeaks) {
} }
} }
TEST_P(ConcurrentMarkAndSweepTest, MultipleMutatorsWeakNewObj) {
std_support::vector<Mutator> mutators(kDefaultThreadCount);
// Make sure all mutators are initialized.
for (int i = 0; i < kDefaultThreadCount; ++i) {
mutators[i].Execute([](mm::ThreadData& threadData, Mutator& mutator) {}).wait();
}
std_support::vector<std::future<void>> gcFutures;
auto epoch = mm::GlobalData::Instance().gc().Schedule();
std::atomic<bool> gcDone = false;
// Spin until thread suspension is requested.
while (!mm::IsThreadSuspensionRequested()) {
}
for (auto& mutator : mutators) {
gcFutures.emplace_back(mutator.Execute([&](mm::ThreadData& threadData, Mutator& mutator) noexcept {
mm::safePoint(threadData);
auto& object = AllocateObject(threadData);
auto& objectWeak = ([&threadData, &object]() -> test_support::RegularWeakReferenceImpl& {
ObjHolder holder;
return InstallWeakReference(threadData, object.header(), holder.slot());
})();
EXPECT_NE(objectWeak.get(), nullptr);
auto& extraObj = *mm::ExtraObjectData::Get(object.header());
extraObj.ClearRegularWeakReferenceImpl();
extraObj.Uninstall();
mm::GlobalData::Instance().gc().DestroyExtraObjectData(extraObj);
while (!gcDone.load(std::memory_order_relaxed)) {
mm::safePoint(threadData);
}
}));
}
mm::GlobalData::Instance().gc().WaitFinalizers(epoch);
gcDone.store(true, std::memory_order_relaxed);
for (auto& future : gcFutures) {
future.wait();
}
}
TEST_P(ConcurrentMarkAndSweepTest, NewThreadsWhileRequestingCollection) { TEST_P(ConcurrentMarkAndSweepTest, NewThreadsWhileRequestingCollection) {
std_support::vector<Mutator> mutators(kDefaultThreadCount); std_support::vector<Mutator> mutators(kDefaultThreadCount);
@@ -1104,31 +1150,6 @@ TEST_P(ConcurrentMarkAndSweepTest, NewThreadsWhileRequestingCollection) {
future.wait(); future.wait();
} }
#ifndef CUSTOM_ALLOCATOR
// Old mutators don't even see alive objects from the new threads yet (as the latter ones have not published anything).
std_support::vector<ObjHeader*> expectedAlive;
for (int i = 0; i < kDefaultThreadCount; ++i) {
expectedAlive.push_back(globals[i]);
expectedAlive.push_back(locals[i]);
expectedAlive.push_back(reachables[i]);
}
for (auto& mutator : mutators) {
EXPECT_THAT(mutator.Alive(), testing::UnorderedElementsAreArray(expectedAlive));
}
for (int i = 0; i < kDefaultThreadCount; ++i) {
std_support::vector<ObjHeader*> aliveForThisThread(expectedAlive.begin(), expectedAlive.end());
aliveForThisThread.push_back(globals[kDefaultThreadCount + i]);
aliveForThisThread.push_back(locals[kDefaultThreadCount + i]);
aliveForThisThread.push_back(reachables[kDefaultThreadCount + i]);
// Unreachables for new threads were not collected.
aliveForThisThread.push_back(unreachables[kDefaultThreadCount + i]);
EXPECT_THAT(newMutators[i].Alive(), testing::UnorderedElementsAreArray(aliveForThisThread));
}
#else
// Custom allocator does not have a notion of objects alive only for some thread
std_support::vector<ObjHeader*> expectedAlive; std_support::vector<ObjHeader*> expectedAlive;
for (int i = 0; i < kDefaultThreadCount; ++i) { for (int i = 0; i < kDefaultThreadCount; ++i) {
expectedAlive.push_back(globals[i]); expectedAlive.push_back(globals[i]);
@@ -1140,9 +1161,27 @@ TEST_P(ConcurrentMarkAndSweepTest, NewThreadsWhileRequestingCollection) {
// Unreachables for new threads were not collected. // Unreachables for new threads were not collected.
expectedAlive.push_back(unreachables[kDefaultThreadCount + i]); expectedAlive.push_back(unreachables[kDefaultThreadCount + i]);
} }
// All threads see the same alive objects with the custom alloctor, enough to check a single mutator.
#ifndef CUSTOM_ALLOCATOR
// Force mutators to publish their internal heaps
std_support::vector<std::future<void>> publishFutures;
for (auto& mutator: mutators) {
publishFutures.emplace_back(mutator.Execute([](mm::ThreadData& threadData, Mutator& mutator) {
threadData.gc().PublishObjectFactory();
}));
}
for (auto& mutator: newMutators) {
publishFutures.emplace_back(mutator.Execute([](mm::ThreadData& threadData, Mutator& mutator) {
threadData.gc().PublishObjectFactory();
}));
}
for (auto& future : publishFutures) {
future.wait();
}
#endif
// All threads see the same alive objects, enough to check a single mutator.
EXPECT_THAT(mutators[0].Alive(), testing::UnorderedElementsAreArray(expectedAlive)); EXPECT_THAT(mutators[0].Alive(), testing::UnorderedElementsAreArray(expectedAlive));
#endif // CUSTOM_ALLOCATOR
} }
TEST_P(ConcurrentMarkAndSweepTest, FreeObjectWithFreeWeakReversedOrder) { TEST_P(ConcurrentMarkAndSweepTest, FreeObjectWithFreeWeakReversedOrder) {
@@ -19,7 +19,7 @@ gc::GC::ThreadData::ThreadData(GC& gc, mm::ThreadData& threadData) noexcept : im
gc::GC::ThreadData::~ThreadData() = default; gc::GC::ThreadData::~ThreadData() = default;
void gc::GC::ThreadData::Publish() noexcept { void gc::GC::ThreadData::PublishObjectFactory() noexcept {
#ifndef CUSTOM_ALLOCATOR #ifndef CUSTOM_ALLOCATOR
impl_->extraObjectDataFactoryThreadQueue().Publish(); impl_->extraObjectDataFactoryThreadQueue().Publish();
impl_->objectFactoryThreadQueue().Publish(); impl_->objectFactoryThreadQueue().Publish();
@@ -36,19 +36,25 @@ void gc::GC::ThreadData::ClearForTests() noexcept {
} }
ALWAYS_INLINE ObjHeader* gc::GC::ThreadData::CreateObject(const TypeInfo* typeInfo) noexcept { ALWAYS_INLINE ObjHeader* gc::GC::ThreadData::CreateObject(const TypeInfo* typeInfo) noexcept {
ObjHeader* obj;
#ifndef CUSTOM_ALLOCATOR #ifndef CUSTOM_ALLOCATOR
return impl_->objectFactoryThreadQueue().CreateObject(typeInfo); obj = impl_->objectFactoryThreadQueue().CreateObject(typeInfo);
#else #else
return impl_->alloc().CreateObject(typeInfo); obj = impl_->alloc().CreateObject(typeInfo);
#endif #endif
impl().gc().barriers().onAllocation(obj);
return obj;
} }
ALWAYS_INLINE ArrayHeader* gc::GC::ThreadData::CreateArray(const TypeInfo* typeInfo, uint32_t elements) noexcept { ALWAYS_INLINE ArrayHeader* gc::GC::ThreadData::CreateArray(const TypeInfo* typeInfo, uint32_t elements) noexcept {
ArrayHeader* arr;
#ifndef CUSTOM_ALLOCATOR #ifndef CUSTOM_ALLOCATOR
return impl_->objectFactoryThreadQueue().CreateArray(typeInfo, elements); arr = impl_->objectFactoryThreadQueue().CreateArray(typeInfo, elements);
#else #else
return impl_->alloc().CreateArray(typeInfo, elements); arr = impl_->alloc().CreateArray(typeInfo, elements);
#endif #endif
impl().gc().barriers().onAllocation(arr->obj());
return arr;
} }
ALWAYS_INLINE mm::ExtraObjectData& gc::GC::ThreadData::CreateExtraObjectDataForObject( ALWAYS_INLINE mm::ExtraObjectData& gc::GC::ThreadData::CreateExtraObjectDataForObject(
@@ -76,6 +82,10 @@ void gc::GC::ThreadData::safePoint() noexcept {
impl_->gc().safePoint(); impl_->gc().safePoint();
} }
void gc::GC::ThreadData::onThreadRegistration() noexcept {
impl_->gc().onThreadRegistration();
}
gc::GC::GC(gcScheduler::GCScheduler& gcScheduler) noexcept : impl_(std_support::make_unique<Impl>(gcScheduler)) {} gc::GC::GC(gcScheduler::GCScheduler& gcScheduler) noexcept : impl_(std_support::make_unique<Impl>(gcScheduler)) {}
gc::GC::~GC() = default; gc::GC::~GC() = default;
@@ -169,7 +169,7 @@ private:
GCHandle gcHandle_ = GCHandle::invalid(); GCHandle gcHandle_ = GCHandle::invalid();
MarkPacer pacer_; MarkPacer pacer_;
std::optional<mm::ThreadRegistry::Iterable> lockedMutatorsList_; std::optional<mm::ThreadRegistry::Iterable> lockedMutatorsList_;
ManuallyScoped<ParallelProcessor> parallelProcessor_; ManuallyScoped<ParallelProcessor> parallelProcessor_{};
std::mutex workerCreationMutex_; std::mutex workerCreationMutex_;
std::atomic<std::size_t> activeWorkersCount_ = 0; std::atomic<std::size_t> activeWorkersCount_ = 0;
@@ -35,7 +35,7 @@ public:
Impl& impl() noexcept { return *impl_; } Impl& impl() noexcept { return *impl_; }
void Publish() noexcept; void PublishObjectFactory() noexcept;
void ClearForTests() noexcept; void ClearForTests() noexcept;
ObjHeader* CreateObject(const TypeInfo* typeInfo) noexcept; ObjHeader* CreateObject(const TypeInfo* typeInfo) noexcept;
@@ -47,6 +47,8 @@ public:
void safePoint() noexcept; void safePoint() noexcept;
void onThreadRegistration() noexcept;
private: private:
std_support::unique_ptr<Impl> impl_; std_support::unique_ptr<Impl> impl_;
}; };
@@ -20,8 +20,12 @@ extern "C" {
void Kotlin_Internal_GC_GCInfoBuilder_setEpoch(KRef thiz, KLong value); void Kotlin_Internal_GC_GCInfoBuilder_setEpoch(KRef thiz, KLong value);
void Kotlin_Internal_GC_GCInfoBuilder_setStartTime(KRef thiz, KLong value); void Kotlin_Internal_GC_GCInfoBuilder_setStartTime(KRef thiz, KLong value);
void Kotlin_Internal_GC_GCInfoBuilder_setEndTime(KRef thiz, KLong value); void Kotlin_Internal_GC_GCInfoBuilder_setEndTime(KRef thiz, KLong value);
void Kotlin_Internal_GC_GCInfoBuilder_setPauseStartTime(KRef thiz, KLong value); void Kotlin_Internal_GC_GCInfoBuilder_setFirstPauseRequestTime(KRef thiz, KLong value);
void Kotlin_Internal_GC_GCInfoBuilder_setPauseEndTime(KRef thiz, KLong value); void Kotlin_Internal_GC_GCInfoBuilder_setFirstPauseStartTime(KRef thiz, KLong value);
void Kotlin_Internal_GC_GCInfoBuilder_setFirstPauseEndTime(KRef thiz, KLong value);
void Kotlin_Internal_GC_GCInfoBuilder_setSecondPauseRequestTime(KRef thiz, KLong value);
void Kotlin_Internal_GC_GCInfoBuilder_setSecondPauseStartTime(KRef thiz, KLong value);
void Kotlin_Internal_GC_GCInfoBuilder_setSecondPauseEndTime(KRef thiz, KLong value);
void Kotlin_Internal_GC_GCInfoBuilder_setPostGcCleanupTime(KRef thiz, KLong value); void Kotlin_Internal_GC_GCInfoBuilder_setPostGcCleanupTime(KRef thiz, KLong value);
void Kotlin_Internal_GC_GCInfoBuilder_setRootSet(KRef thiz, void Kotlin_Internal_GC_GCInfoBuilder_setRootSet(KRef thiz,
KLong threadLocalReferences, KLong stackReferences, KLong threadLocalReferences, KLong stackReferences,
@@ -77,8 +81,15 @@ struct GCInfo {
std::optional<uint64_t> epoch; std::optional<uint64_t> epoch;
std::optional<KLong> startTime; // time since process start std::optional<KLong> startTime; // time since process start
std::optional<KLong> endTime; std::optional<KLong> endTime;
std::optional<KLong> pauseStartTime;
std::optional<KLong> pauseEndTime; std::optional<KLong> firstPauseRequestTime;
std::optional<KLong> firstPauseStartTime;
std::optional<KLong> firstPauseEndTime;
std::optional<KLong> secondPauseRequestTime;
std::optional<KLong> secondPauseStartTime;
std::optional<KLong> secondPauseEndTime;
std::optional<KLong> finalizersDoneTime; std::optional<KLong> finalizersDoneTime;
std::optional<RootSetStatistics> rootSet; std::optional<RootSetStatistics> rootSet;
std::optional<kotlin::gc::MarkStats> markStats; std::optional<kotlin::gc::MarkStats> markStats;
@@ -91,8 +102,12 @@ struct GCInfo {
Kotlin_Internal_GC_GCInfoBuilder_setEpoch(builder, static_cast<KLong>(*epoch)); Kotlin_Internal_GC_GCInfoBuilder_setEpoch(builder, static_cast<KLong>(*epoch));
if (startTime) Kotlin_Internal_GC_GCInfoBuilder_setStartTime(builder, *startTime); if (startTime) Kotlin_Internal_GC_GCInfoBuilder_setStartTime(builder, *startTime);
if (endTime) Kotlin_Internal_GC_GCInfoBuilder_setEndTime(builder, *endTime); if (endTime) Kotlin_Internal_GC_GCInfoBuilder_setEndTime(builder, *endTime);
if (pauseStartTime) Kotlin_Internal_GC_GCInfoBuilder_setPauseStartTime(builder, *pauseStartTime); if (firstPauseRequestTime) Kotlin_Internal_GC_GCInfoBuilder_setFirstPauseRequestTime(builder, *firstPauseRequestTime);
if (pauseEndTime) Kotlin_Internal_GC_GCInfoBuilder_setPauseEndTime(builder, *pauseEndTime); if (firstPauseStartTime) Kotlin_Internal_GC_GCInfoBuilder_setFirstPauseStartTime(builder, *firstPauseStartTime);
if (firstPauseEndTime) Kotlin_Internal_GC_GCInfoBuilder_setFirstPauseEndTime(builder, *firstPauseEndTime);
if (secondPauseRequestTime) Kotlin_Internal_GC_GCInfoBuilder_setSecondPauseRequestTime(builder, *secondPauseRequestTime);
if (secondPauseStartTime) Kotlin_Internal_GC_GCInfoBuilder_setSecondPauseStartTime(builder, *secondPauseStartTime);
if (secondPauseEndTime) Kotlin_Internal_GC_GCInfoBuilder_setSecondPauseEndTime(builder, *secondPauseEndTime);
if (finalizersDoneTime) Kotlin_Internal_GC_GCInfoBuilder_setPostGcCleanupTime(builder, *finalizersDoneTime); if (finalizersDoneTime) Kotlin_Internal_GC_GCInfoBuilder_setPostGcCleanupTime(builder, *finalizersDoneTime);
if (rootSet) if (rootSet)
Kotlin_Internal_GC_GCInfoBuilder_setRootSet( Kotlin_Internal_GC_GCInfoBuilder_setRootSet(
@@ -223,9 +238,21 @@ void GCHandle::finished() {
epoch_, "Heap memory usage: before %" PRIu64 " bytes, after %" PRIu64 " bytes", stat->memoryUsageBefore.heap->sizeBytes, epoch_, "Heap memory usage: before %" PRIu64 " bytes, after %" PRIu64 " bytes", stat->memoryUsageBefore.heap->sizeBytes,
stat->memoryUsageAfter.heap->sizeBytes); stat->memoryUsageAfter.heap->sizeBytes);
} }
if (stat->pauseStartTime && stat->pauseEndTime) { if (stat->firstPauseRequestTime && stat->firstPauseStartTime) {
auto time = (*stat->pauseEndTime - *stat->pauseStartTime) / 1000; auto time = (*stat->firstPauseStartTime - *stat->firstPauseRequestTime) / 1000;
GCLogInfo(epoch_, "Mutators pause time: %" PRIu64 " microseconds.", time); GCLogInfo(epoch_, "Time to pause #1: %" PRIu64 " microseconds.", time);
}
if (stat->firstPauseRequestTime && stat->firstPauseEndTime) {
auto time = (*stat->firstPauseEndTime - *stat->firstPauseRequestTime) / 1000;
GCLogInfo(epoch_, "Mutators pause time #1: %" PRIu64 " microseconds.", time);
}
if (stat->secondPauseRequestTime && stat->secondPauseStartTime) {
auto time = (*stat->secondPauseStartTime - *stat->secondPauseRequestTime) / 1000;
GCLogInfo(epoch_, "Time to pause #2: %" PRIu64 " microseconds.", time);
}
if (stat->secondPauseRequestTime && stat->secondPauseEndTime) {
auto time = (*stat->secondPauseEndTime - *stat->secondPauseRequestTime) / 1000;
GCLogInfo(epoch_, "Mutators pause time #2: %" PRIu64 " microseconds.", time);
} }
if (stat->startTime) { if (stat->startTime) {
auto time = (*current.endTime - *current.startTime) / 1000; auto time = (*current.endTime - *current.startTime) / 1000;
@@ -242,14 +269,30 @@ void GCHandle::suspensionRequested() {
std::lock_guard guard(lock); std::lock_guard guard(lock);
GCLogDebug(epoch_, "Requested thread suspension"); GCLogDebug(epoch_, "Requested thread suspension");
if (auto* stat = statByEpoch(epoch_)) { if (auto* stat = statByEpoch(epoch_)) {
stat->pauseStartTime = static_cast<KLong>(konan::getTimeNanos()); auto requestTime = static_cast<KLong>(konan::getTimeNanos());
if (!stat->firstPauseRequestTime) {
stat->firstPauseRequestTime = requestTime;
} else {
RuntimeAssert(!stat->secondPauseRequestTime, "GCStatistics support max two pauses per GC epoch");
stat->secondPauseRequestTime = requestTime;
}
} }
} }
void GCHandle::threadsAreSuspended() { void GCHandle::threadsAreSuspended() {
std::lock_guard guard(lock); std::lock_guard guard(lock);
if (auto* stat = statByEpoch(epoch_)) { if (auto* stat = statByEpoch(epoch_)) {
if (stat->pauseStartTime) { auto startTime = static_cast<KLong>(konan::getTimeNanos());
auto time = (konan::getTimeNanos() - *stat->pauseStartTime) / 1000; std::optional<KLong> requestTime;
if (!stat->firstPauseStartTime) {
stat->firstPauseStartTime = startTime;
requestTime = stat->firstPauseRequestTime;
} else {
RuntimeAssert(!stat->secondPauseStartTime, "GCStatistics support max two pauses per GC epoch");
stat->secondPauseStartTime = startTime;
requestTime = stat->secondPauseRequestTime;
}
if (requestTime) {
auto time = (startTime - *requestTime) / 1000;
GCLogDebug(epoch_, "Suspended all threads in %" PRIu64 " microseconds", time); GCLogDebug(epoch_, "Suspended all threads in %" PRIu64 " microseconds", time);
return; return;
} }
@@ -267,9 +310,18 @@ void GCHandle::threadsAreSuspended() {
void GCHandle::threadsAreResumed() { void GCHandle::threadsAreResumed() {
std::lock_guard guard(lock); std::lock_guard guard(lock);
if (auto* stat = statByEpoch(epoch_)) { if (auto* stat = statByEpoch(epoch_)) {
stat->pauseEndTime = static_cast<KLong>(konan::getTimeNanos()); auto endTime = static_cast<KLong>(konan::getTimeNanos());
if (stat->pauseStartTime) { std::optional<KLong> startTime;
auto time = (*stat->pauseEndTime - *stat->pauseStartTime) / 1000; if (!stat->firstPauseEndTime) {
stat->firstPauseEndTime = endTime;
startTime = stat->firstPauseStartTime;
} else {
RuntimeAssert(!stat->secondPauseEndTime, "GCStatistics support max two pauses per GC epoch");
stat->secondPauseEndTime = endTime;
startTime = stat->secondPauseStartTime;
}
if (startTime) {
auto time = (endTime - *startTime) / 1000;
GCLogDebug(epoch_, "Resume all threads. Total pause time is %" PRId64 " microseconds.", time); GCLogDebug(epoch_, "Resume all threads. Total pause time is %" PRId64 " microseconds.", time);
return; return;
} }
@@ -358,6 +410,8 @@ void GCHandle::sweptExtraObjects(gc::SweepStats stats) noexcept {
} }
} }
GCHandle::GCMarkScope GCHandle::mark() { return GCMarkScope(*this); }
GCHandle::GCSweepScope::GCSweepScope(kotlin::gc::GCHandle& handle) : handle_(handle) {} GCHandle::GCSweepScope::GCSweepScope(kotlin::gc::GCHandle& handle) : handle_(handle) {}
GCHandle::GCSweepScope::~GCSweepScope() { GCHandle::GCSweepScope::~GCSweepScope() {
@@ -397,11 +451,27 @@ GCHandle::GCThreadRootSetScope::~GCThreadRootSetScope(){
threadData_.threadId(), stackRoots_, threadLocalRoots_, getStageTime()); threadData_.threadId(), stackRoots_, threadLocalRoots_, getStageTime());
} }
GCHandle::GCMarkScope::GCMarkScope(kotlin::gc::GCHandle& handle) : handle_(handle){} void GCHandle::GCMarkScope::swap(GCHandle::GCMarkScope& other) noexcept {
std::swap(handle_, other.handle_);
std::swap(startTime_, other.startTime_);
}
GCHandle::GCMarkScope::GCMarkScope(kotlin::gc::GCHandle& handle) : handle_(handle) {}
GCHandle::GCMarkScope::GCMarkScope(GCHandle::GCMarkScope&& that) noexcept {
swap(that);
}
GCHandle::GCMarkScope& GCHandle::GCMarkScope::operator=(GCHandle::GCMarkScope that) noexcept {
swap(that);
return *this;
}
GCHandle::GCMarkScope::~GCMarkScope() { GCHandle::GCMarkScope::~GCMarkScope() {
handle_.marked(stats_); if (handle_.isValid()) {
GCLogDebug(handle_.getEpoch(), "Marked %" PRIu64 " objects in %" PRIu64 " microseconds.", stats_.markedCount, getStageTime()); handle_.marked(stats_);
GCLogDebug(handle_.getEpoch(), "Marked %" PRIu64 " objects in %" PRIu64 " microseconds.", stats_.markedCount, getStageTime());
}
} }
gc::GCHandle::GCProcessWeaksScope::GCProcessWeaksScope(gc::GCHandle& handle) noexcept : handle_(handle) {} gc::GCHandle::GCProcessWeaksScope::GCProcessWeaksScope(gc::GCHandle& handle) noexcept : handle_(handle) {}
@@ -95,16 +95,7 @@ public:
void addThreadLocalRoot() { threadLocalRoots_++; } void addThreadLocalRoot() { threadLocalRoots_++; }
}; };
class GCMarkScope : GCStageScopeUsTimer, Pinned { class GCMarkScope;
GCHandle& handle_;
MarkStats stats_;
public:
explicit GCMarkScope(GCHandle& handle);
~GCMarkScope();
void addObject() noexcept { ++stats_.markedCount; }
};
class GCProcessWeaksScope : GCStageScopeUsTimer, Pinned { class GCProcessWeaksScope : GCStageScopeUsTimer, Pinned {
GCHandle& handle_; GCHandle& handle_;
@@ -151,9 +142,24 @@ public:
GCSweepExtraObjectsScope sweepExtraObjects() { return GCSweepExtraObjectsScope(*this); } GCSweepExtraObjectsScope sweepExtraObjects() { return GCSweepExtraObjectsScope(*this); }
GCGlobalRootSetScope collectGlobalRoots() { return GCGlobalRootSetScope(*this); } GCGlobalRootSetScope collectGlobalRoots() { return GCGlobalRootSetScope(*this); }
GCThreadRootSetScope collectThreadRoots(mm::ThreadData& threadData) { return GCThreadRootSetScope(*this, threadData); } GCThreadRootSetScope collectThreadRoots(mm::ThreadData& threadData) { return GCThreadRootSetScope(*this, threadData); }
GCMarkScope mark() { return GCMarkScope(*this); } GCMarkScope mark();
GCProcessWeaksScope processWeaks() noexcept { return GCProcessWeaksScope(*this); } GCProcessWeaksScope processWeaks() noexcept { return GCProcessWeaksScope(*this); }
MarkStats getMarked(); MarkStats getMarked();
}; };
class GCHandle::GCMarkScope : GCStageScopeUsTimer {
GCHandle handle_ = GCHandle::invalid();
MarkStats stats_;
void swap(GCMarkScope& other) noexcept;
public:
explicit GCMarkScope(GCHandle& handle);
GCMarkScope(GCMarkScope&& that) noexcept;
GCMarkScope& operator=(GCMarkScope that) noexcept;
~GCMarkScope();
void addObject() noexcept { ++stats_.markedCount; }
};
} }
@@ -20,7 +20,7 @@ gc::GC::ThreadData::ThreadData(GC& gc, mm::ThreadData& threadData) noexcept : im
gc::GC::ThreadData::~ThreadData() = default; gc::GC::ThreadData::~ThreadData() = default;
void gc::GC::ThreadData::Publish() noexcept { void gc::GC::ThreadData::PublishObjectFactory() noexcept {
#ifndef CUSTOM_ALLOCATOR #ifndef CUSTOM_ALLOCATOR
impl_->extraObjectDataFactoryThreadQueue().Publish(); impl_->extraObjectDataFactoryThreadQueue().Publish();
impl_->objectFactoryThreadQueue().Publish(); impl_->objectFactoryThreadQueue().Publish();
@@ -73,6 +73,8 @@ void gc::GC::ThreadData::OnSuspendForGC() noexcept { }
void gc::GC::ThreadData::safePoint() noexcept {} void gc::GC::ThreadData::safePoint() noexcept {}
void gc::GC::ThreadData::onThreadRegistration() noexcept {}
gc::GC::GC(gcScheduler::GCScheduler&) noexcept : impl_(std_support::make_unique<Impl>()) {} gc::GC::GC(gcScheduler::GCScheduler&) noexcept : impl_(std_support::make_unique<Impl>()) {}
gc::GC::~GC() = default; gc::GC::~GC() = default;
@@ -20,7 +20,7 @@ gc::GC::ThreadData::ThreadData(GC& gc, mm::ThreadData& threadData) noexcept : im
gc::GC::ThreadData::~ThreadData() = default; gc::GC::ThreadData::~ThreadData() = default;
void gc::GC::ThreadData::Publish() noexcept { void gc::GC::ThreadData::PublishObjectFactory() noexcept {
#ifndef CUSTOM_ALLOCATOR #ifndef CUSTOM_ALLOCATOR
impl_->extraObjectDataFactoryThreadQueue().Publish(); impl_->extraObjectDataFactoryThreadQueue().Publish();
impl_->objectFactoryThreadQueue().Publish(); impl_->objectFactoryThreadQueue().Publish();
@@ -73,6 +73,8 @@ void gc::GC::ThreadData::OnSuspendForGC() noexcept { }
void gc::GC::ThreadData::safePoint() noexcept {} void gc::GC::ThreadData::safePoint() noexcept {}
void gc::GC::ThreadData::onThreadRegistration() noexcept {}
gc::GC::GC(gcScheduler::GCScheduler& gcScheduler) noexcept : impl_(std_support::make_unique<Impl>(gcScheduler)) {} gc::GC::GC(gcScheduler::GCScheduler& gcScheduler) noexcept : impl_(std_support::make_unique<Impl>(gcScheduler)) {}
gc::GC::~GC() = default; gc::GC::~GC() = default;
@@ -99,6 +99,10 @@ void gc::SameThreadMarkAndSweep::PerformFullGC(int64_t epoch) noexcept {
gc::processWeaks<DefaultProcessWeaksTraits>(gcHandle, mm::SpecialRefRegistry::instance()); gc::processWeaks<DefaultProcessWeaksTraits>(gcHandle, mm::SpecialRefRegistry::instance());
#ifndef CUSTOM_ALLOCATOR #ifndef CUSTOM_ALLOCATOR
for (auto& thread : kotlin::mm::ThreadRegistry::Instance().LockForIter()) {
thread.gc().PublishObjectFactory();
}
// Taking the locks before the pause is completed. So that any destroying thread // Taking the locks before the pause is completed. So that any destroying thread
// would not publish into the global state at an unexpected time. // would not publish into the global state at an unexpected time.
std::optional extraObjectFactoryIterable = extraObjectDataFactory_.LockForIter(); std::optional extraObjectFactoryIterable = extraObjectDataFactory_.LockForIter();
@@ -1010,6 +1010,52 @@ TEST_F(SameThreadMarkAndSweepTest, MultipleMutatorsWeaks) {
} }
} }
TEST_F(SameThreadMarkAndSweepTest, MultipleMutatorsWeakNewObj) {
std_support::vector<Mutator> mutators(kDefaultThreadCount);
// Make sure all mutators are initialized.
for (int i = 0; i < kDefaultThreadCount; ++i) {
mutators[i].Execute([](mm::ThreadData& threadData, Mutator& mutator) {}).wait();
}
std_support::vector<std::future<void>> gcFutures;
auto epoch = mm::GlobalData::Instance().gc().Schedule();
std::atomic<bool> gcDone = false;
// Spin until thread suspension is requested.
while (!mm::IsThreadSuspensionRequested()) {
}
for (auto& mutator : mutators) {
gcFutures.emplace_back(mutator.Execute([&](mm::ThreadData& threadData, Mutator& mutator) noexcept {
mm::safePoint(threadData);
auto& object = AllocateObject(threadData);
auto& objectWeak = ([&threadData, &object]() -> test_support::RegularWeakReferenceImpl& {
ObjHolder holder;
return InstallWeakReference(threadData, object.header(), holder.slot());
})();
EXPECT_NE(objectWeak.get(), nullptr);
auto& extraObj = *mm::ExtraObjectData::Get(object.header());
extraObj.ClearRegularWeakReferenceImpl();
extraObj.Uninstall();
mm::GlobalData::Instance().gc().DestroyExtraObjectData(extraObj);
while (!gcDone.load(std::memory_order_relaxed)) {
mm::safePoint(threadData);
}
}));
}
mm::GlobalData::Instance().gc().WaitFinalizers(epoch);
gcDone.store(true, std::memory_order_relaxed);
for (auto& future : gcFutures) {
future.wait();
}
}
TEST_F(SameThreadMarkAndSweepTest, NewThreadsWhileRequestingCollection) { TEST_F(SameThreadMarkAndSweepTest, NewThreadsWhileRequestingCollection) {
std_support::vector<Mutator> mutators(kDefaultThreadCount); std_support::vector<Mutator> mutators(kDefaultThreadCount);
std_support::vector<ObjHeader*> globals(2 * kDefaultThreadCount); std_support::vector<ObjHeader*> globals(2 * kDefaultThreadCount);
@@ -1079,31 +1125,6 @@ TEST_F(SameThreadMarkAndSweepTest, NewThreadsWhileRequestingCollection) {
future.wait(); future.wait();
} }
#ifndef CUSTOM_ALLOCATOR
// Old mutators don't even see alive objects from the new threads yet (as the latter ones have not published anything).
std_support::vector<ObjHeader*> expectedAlive;
for (int i = 0; i < kDefaultThreadCount; ++i) {
expectedAlive.push_back(globals[i]);
expectedAlive.push_back(locals[i]);
expectedAlive.push_back(reachables[i]);
}
for (auto& mutator : mutators) {
EXPECT_THAT(mutator.Alive(), testing::UnorderedElementsAreArray(expectedAlive));
}
for (int i = 0; i < kDefaultThreadCount; ++i) {
std_support::vector<ObjHeader*> aliveForThisThread(expectedAlive.begin(), expectedAlive.end());
aliveForThisThread.push_back(globals[kDefaultThreadCount + i]);
aliveForThisThread.push_back(locals[kDefaultThreadCount + i]);
aliveForThisThread.push_back(reachables[kDefaultThreadCount + i]);
// Unreachables for new threads were not collected.
aliveForThisThread.push_back(unreachables[kDefaultThreadCount + i]);
EXPECT_THAT(newMutators[i].Alive(), testing::UnorderedElementsAreArray(aliveForThisThread));
}
#else
// Custom allocator does not have a notion of objects alive only for some thread
std_support::vector<ObjHeader*> expectedAlive; std_support::vector<ObjHeader*> expectedAlive;
for (int i = 0; i < kDefaultThreadCount; ++i) { for (int i = 0; i < kDefaultThreadCount; ++i) {
expectedAlive.push_back(globals[i]); expectedAlive.push_back(globals[i]);
@@ -1115,9 +1136,27 @@ TEST_F(SameThreadMarkAndSweepTest, NewThreadsWhileRequestingCollection) {
// Unreachables for new threads were not collected. // Unreachables for new threads were not collected.
expectedAlive.push_back(unreachables[kDefaultThreadCount + i]); expectedAlive.push_back(unreachables[kDefaultThreadCount + i]);
} }
// All threads see the same alive objects with the custom alloctor, enough to check a single mutator.
#ifndef CUSTOM_ALLOCATOR
// Force mutators to publish their internal heaps
std_support::vector<std::future<void>> publishFutures;
for (auto& mutator: mutators) {
publishFutures.emplace_back(mutator.Execute([](mm::ThreadData& threadData, Mutator& mutator) {
threadData.gc().PublishObjectFactory();
}));
}
for (auto& mutator: newMutators) {
publishFutures.emplace_back(mutator.Execute([](mm::ThreadData& threadData, Mutator& mutator) {
threadData.gc().PublishObjectFactory();
}));
}
for (auto& future : publishFutures) {
future.wait();
}
#endif
// All threads see the same alive objects, enough to check a single mutator.
EXPECT_THAT(mutators[0].Alive(), testing::UnorderedElementsAreArray(expectedAlive)); EXPECT_THAT(mutators[0].Alive(), testing::UnorderedElementsAreArray(expectedAlive));
#endif // CUSTOM_ALLOCATOR
} }
@@ -53,10 +53,16 @@ public class RootSetStatistics(
* @property startTimeNs Time, when garbage collector run is started, meausered by [kotlin.system.getTimeNanos]. * @property startTimeNs Time, when garbage collector run is started, meausered by [kotlin.system.getTimeNanos].
* @property endTimeNs Time, when garbage collector run is ended, measured by [kotlin.system.getTimeNanos]. * @property endTimeNs Time, when garbage collector run is ended, measured by [kotlin.system.getTimeNanos].
* After this point, most of the memory is reclaimed, and a new garbage collector run can start. * After this point, most of the memory is reclaimed, and a new garbage collector run can start.
* @property pauseStartTimeNs Time, when mutator threads are suspended, mesured by [kotlin.system.getTimeNanos]. * @property firstPauseRequestTimeNs Time, when the garbage collector thread requested suspension of mutator threads for the first time,
* @property pauseEndTimeNs Time, when mutator threads are unsuspended, mesured by [kotlin.system.getTimeNanos]. * mesured by [kotlin.system.getTimeNanos].
* @property firstPauseStartTimeNs Time, when mutator threads are suspended for the first time, mesured by [kotlin.system.getTimeNanos].
* @property firstPauseEndTimeNs Time, when mutator threads are unsuspended for the first time, mesured by [kotlin.system.getTimeNanos].
* @property secondPauseRequestTimeNs Time, when the garbage collector thread requested suspension of mutator threads for the second time,
* mesured by [kotlin.system.getTimeNanos].
* @property secondPauseStartTimeNs Time, when mutator threads are suspended for the second time, mesured by [kotlin.system.getTimeNanos].
* @property secondPauseEndTimeNs Time, when mutator threads are unsuspended for the second time, mesured by [kotlin.system.getTimeNanos].
* @property postGcCleanupTimeNs Time, when all memory is reclaimed, measured by [kotlin.system.getTimeNanos]. * @property postGcCleanupTimeNs Time, when all memory is reclaimed, measured by [kotlin.system.getTimeNanos].
* If null, memory reclamation is still in progress. * If null, memory reclamation is still in progress.
* @property rootSet The number of objects in each root set pool. Check [RootSetStatistics] doc for details. * @property rootSet The number of objects in each root set pool. Check [RootSetStatistics] doc for details.
* @property memoryUsageAfter Memory usage at the start of garbage collector run, separated by memory pools. * @property memoryUsageAfter Memory usage at the start of garbage collector run, separated by memory pools.
* The set of memory pools depends on the collector implementation. * The set of memory pools depends on the collector implementation.
@@ -74,8 +80,12 @@ public class GCInfo(
val epoch: Long, val epoch: Long,
val startTimeNs: Long, val startTimeNs: Long,
val endTimeNs: Long, val endTimeNs: Long,
val pauseStartTimeNs: Long, val firstPauseRequestTimeNs: Long,
val pauseEndTimeNs: Long, val firstPauseStartTimeNs: Long,
val firstPauseEndTimeNs: Long,
val secondPauseRequestTimeNs: Long?,
val secondPauseStartTimeNs: Long?,
val secondPauseEndTimeNs: Long?,
val postGcCleanupTimeNs: Long?, val postGcCleanupTimeNs: Long?,
val rootSet: RootSetStatistics, val rootSet: RootSetStatistics,
val memoryUsageBefore: Map<String, MemoryUsage>, val memoryUsageBefore: Map<String, MemoryUsage>,
@@ -89,8 +99,12 @@ public class GCInfo(
info.epoch, info.epoch,
info.startTimeNs, info.startTimeNs,
info.endTimeNs, info.endTimeNs,
info.pauseStartTimeNs, info.firstPauseRequestTimeNs,
info.pauseEndTimeNs, info.firstPauseStartTimeNs,
info.firstPauseEndTimeNs,
info.secondPauseRequestTimeNs,
info.secondPauseStartTimeNs,
info.secondPauseEndTimeNs,
info.postGcCleanupTimeNs, info.postGcCleanupTimeNs,
info.rootSet.let { info.rootSet.let {
RootSetStatistics( RootSetStatistics(
@@ -71,8 +71,14 @@ public class RootSetStatistics(
* @property startTimeNs Time, when garbage collector run is started, meausered by [kotlin.system.getTimeNanos]. * @property startTimeNs Time, when garbage collector run is started, meausered by [kotlin.system.getTimeNanos].
* @property endTimeNs Time, when garbage collector run is ended, measured by [kotlin.system.getTimeNanos]. * @property endTimeNs Time, when garbage collector run is ended, measured by [kotlin.system.getTimeNanos].
* After this point, most of the memory is reclaimed, and a new garbage collector run can start. * After this point, most of the memory is reclaimed, and a new garbage collector run can start.
* @property pauseStartTimeNs Time, when mutator threads are suspended, mesured by [kotlin.system.getTimeNanos]. * @property firstPauseRequestTimeNs Time, when the garbage collector thread requested suspension of mutator threads for the first time,
* @property pauseEndTimeNs Time, when mutator threads are unsuspended, mesured by [kotlin.system.getTimeNanos]. * mesured by [kotlin.system.getTimeNanos].
* @property firstPauseStartTimeNs Time, when mutator threads are suspended for the first time, mesured by [kotlin.system.getTimeNanos].
* @property firstPauseEndTimeNs Time, when mutator threads are unsuspended for the first time, mesured by [kotlin.system.getTimeNanos].
* @property secondPauseRequestTimeNs Time, when the garbage collector thread requested suspension of mutator threads for the second time,
* mesured by [kotlin.system.getTimeNanos].
* @property secondPauseStartTimeNs Time, when mutator threads are suspended for the second time, mesured by [kotlin.system.getTimeNanos].
* @property secondPauseEndTimeNs Time, when mutator threads are unsuspended for the second time, mesured by [kotlin.system.getTimeNanos].
* @property postGcCleanupTimeNs Time, when all memory is reclaimed, measured by [kotlin.system.getTimeNanos]. * @property postGcCleanupTimeNs Time, when all memory is reclaimed, measured by [kotlin.system.getTimeNanos].
* If null, memory reclamation is still in progress. * If null, memory reclamation is still in progress.
* @property rootSet The number of objects in each root set pool. Check [RootSetStatistics] doc for details. * @property rootSet The number of objects in each root set pool. Check [RootSetStatistics] doc for details.
@@ -93,8 +99,12 @@ public class GCInfo(
val epoch: Long, val epoch: Long,
val startTimeNs: Long, val startTimeNs: Long,
val endTimeNs: Long, val endTimeNs: Long,
val pauseStartTimeNs: Long, val firstPauseRequestTimeNs: Long,
val pauseEndTimeNs: Long, val firstPauseStartTimeNs: Long,
val firstPauseEndTimeNs: Long,
val secondPauseRequestTimeNs: Long?,
val secondPauseStartTimeNs: Long?,
val secondPauseEndTimeNs: Long?,
val postGcCleanupTimeNs: Long?, val postGcCleanupTimeNs: Long?,
val rootSet: RootSetStatistics, val rootSet: RootSetStatistics,
val markedCount: Long, val markedCount: Long,
@@ -116,8 +126,12 @@ private class GCInfoBuilder() {
var epoch: Long? = null var epoch: Long? = null
var startTimeNs: Long? = null var startTimeNs: Long? = null
var endTimeNs: Long? = null var endTimeNs: Long? = null
var pauseStartTimeNs: Long? = null var firstPauseRequestTimeNs: Long? = null
var pauseEndTimeNs: Long? = null var firstPauseStartTimeNs: Long? = null
var firstPauseEndTimeNs: Long? = null
var secondPauseRequestTimeNs: Long? = null
var secondPauseStartTimeNs: Long? = null
var secondPauseEndTimeNs: Long? = null
var postGcCleanupTimeNs: Long? = null var postGcCleanupTimeNs: Long? = null
var rootSet: RootSetStatistics? = null var rootSet: RootSetStatistics? = null
var markedCount: Long? = null var markedCount: Long? = null
@@ -140,14 +154,34 @@ private class GCInfoBuilder() {
endTimeNs = value endTimeNs = value
} }
@ExportForCppRuntime("Kotlin_Internal_GC_GCInfoBuilder_setPauseStartTime") @ExportForCppRuntime("Kotlin_Internal_GC_GCInfoBuilder_setFirstPauseRequestTime")
private fun setPauseStartTime(value: Long) { private fun setFirstPauseRequestTime(value: Long) {
pauseStartTimeNs = value firstPauseRequestTimeNs = value
} }
@ExportForCppRuntime("Kotlin_Internal_GC_GCInfoBuilder_setPauseEndTime") @ExportForCppRuntime("Kotlin_Internal_GC_GCInfoBuilder_setFirstPauseStartTime")
private fun setPauseEndTime(value: Long) { private fun setFirstPauseStartTime(value: Long) {
pauseEndTimeNs = value firstPauseStartTimeNs = value
}
@ExportForCppRuntime("Kotlin_Internal_GC_GCInfoBuilder_setFirstPauseEndTime")
private fun setFirstPauseEndTime(value: Long) {
firstPauseEndTimeNs = value
}
@ExportForCppRuntime("Kotlin_Internal_GC_GCInfoBuilder_setSecondPauseRequestTime")
private fun setSecondPauseRequestTime(value: Long) {
secondPauseRequestTimeNs = value
}
@ExportForCppRuntime("Kotlin_Internal_GC_GCInfoBuilder_setSecondPauseStartTime")
private fun setSecondPauseStartTime(value: Long) {
secondPauseStartTimeNs = value
}
@ExportForCppRuntime("Kotlin_Internal_GC_GCInfoBuilder_setSecondPauseEndTime")
private fun setSecondPauseEndTime(value: Long) {
secondPauseEndTimeNs = value
} }
@ExportForCppRuntime("Kotlin_Internal_GC_GCInfoBuilder_setPostGcCleanupTime") @ExportForCppRuntime("Kotlin_Internal_GC_GCInfoBuilder_setPostGcCleanupTime")
@@ -191,8 +225,12 @@ private class GCInfoBuilder() {
epoch ?: return null, epoch ?: return null,
startTimeNs ?: return null, startTimeNs ?: return null,
endTimeNs ?: return null, endTimeNs ?: return null,
pauseStartTimeNs ?: return null, firstPauseRequestTimeNs ?: return null,
pauseEndTimeNs ?: return null, firstPauseStartTimeNs ?: return null,
firstPauseEndTimeNs ?: return null,
secondPauseRequestTimeNs,
secondPauseStartTimeNs,
secondPauseEndTimeNs,
postGcCleanupTimeNs, postGcCleanupTimeNs,
rootSet ?: return null, rootSet ?: return null,
markedCount ?: return null, markedCount ?: return null,
@@ -78,7 +78,7 @@ TEST_F(ExtraObjectDataTest, ConcurrentInstall) {
} }
auto& extraData = mm::ExtraObjectData::Install(object.header()); auto& extraData = mm::ExtraObjectData::Install(object.header());
actual[i] = &extraData; actual[i] = &extraData;
mm::GlobalData::Instance().threadRegistry().CurrentThreadData()->Publish(); mm::GlobalData::Instance().threadRegistry().CurrentThreadData()->gc().PublishObjectFactory();
}); });
} }
@@ -214,7 +214,12 @@ public:
owner_.all_.splice(owner_.all_.end(), std::move(queue_)); owner_.all_.splice(owner_.all_.end(), std::move(queue_));
} }
void clearForTests() noexcept { queue_.clear(); } void clearForTests() noexcept {
for (auto& specialRef: queue_) {
specialRef.dispose();
}
queue_.clear();
}
[[nodiscard("must be manually disposed")]] StableRef createStableRef(ObjHeader* object) noexcept; [[nodiscard("must be manually disposed")]] StableRef createStableRef(ObjHeader* object) noexcept;
[[nodiscard("must be manually disposed")]] WeakRef createWeakRef(ObjHeader* object) noexcept; [[nodiscard("must be manually disposed")]] WeakRef createWeakRef(ObjHeader* object) noexcept;
@@ -63,7 +63,6 @@ public:
// TODO: These use separate locks, which is inefficient. // TODO: These use separate locks, which is inefficient.
globalsThreadQueue_.Publish(); globalsThreadQueue_.Publish();
specialRefRegistry_.publish(); specialRefRegistry_.publish();
gc_.Publish();
} }
void ClearForTests() noexcept { void ClearForTests() noexcept {
@@ -18,11 +18,13 @@ mm::ThreadRegistry& mm::ThreadRegistry::Instance() noexcept {
} }
mm::ThreadRegistry::Node* mm::ThreadRegistry::RegisterCurrentThread() noexcept { mm::ThreadRegistry::Node* mm::ThreadRegistry::RegisterCurrentThread() noexcept {
auto lock = list_.LockForIter();
auto* threadDataNode = list_.Emplace(konan::currentThreadId()); auto* threadDataNode = list_.Emplace(konan::currentThreadId());
AssertThreadState(threadDataNode->Get(), ThreadState::kNative); AssertThreadState(threadDataNode->Get(), ThreadState::kNative);
Node*& currentDataNode = currentThreadDataNode_; Node*& currentDataNode = currentThreadDataNode_;
RuntimeAssert(!IsCurrentThreadRegistered(), "This thread already had some data assigned to it."); RuntimeAssert(!IsCurrentThreadRegistered(), "This thread already had some data assigned to it.");
currentDataNode = threadDataNode; currentDataNode = threadDataNode;
threadDataNode->Get()->gc().onThreadRegistration();
return threadDataNode; return threadDataNode;
} }
@@ -248,10 +248,22 @@ void Kotlin_Internal_GC_GCInfoBuilder_setStartTime(KRef thiz, KLong value) {
void Kotlin_Internal_GC_GCInfoBuilder_setEndTime(KRef thiz, KLong value) { void Kotlin_Internal_GC_GCInfoBuilder_setEndTime(KRef thiz, KLong value) {
throw std::runtime_error("Not implemented for tests"); throw std::runtime_error("Not implemented for tests");
} }
void Kotlin_Internal_GC_GCInfoBuilder_setPauseStartTime(KRef thiz, KLong value) { void Kotlin_Internal_GC_GCInfoBuilder_setFirstPauseRequestTime(KRef thiz, KLong value) {
throw std::runtime_error("Not implemented for tests"); throw std::runtime_error("Not implemented for tests");
} }
void Kotlin_Internal_GC_GCInfoBuilder_setPauseEndTime(KRef thiz, KLong value) { void Kotlin_Internal_GC_GCInfoBuilder_setFirstPauseStartTime(KRef thiz, KLong value) {
throw std::runtime_error("Not implemented for tests");
}
void Kotlin_Internal_GC_GCInfoBuilder_setFirstPauseEndTime(KRef thiz, KLong value) {
throw std::runtime_error("Not implemented for tests");
}
void Kotlin_Internal_GC_GCInfoBuilder_setSecondPauseRequestTime(KRef thiz, KLong value) {
throw std::runtime_error("Not implemented for tests");
}
void Kotlin_Internal_GC_GCInfoBuilder_setSecondPauseStartTime(KRef thiz, KLong value) {
throw std::runtime_error("Not implemented for tests");
}
void Kotlin_Internal_GC_GCInfoBuilder_setSecondPauseEndTime(KRef thiz, KLong value) {
throw std::runtime_error("Not implemented for tests"); throw std::runtime_error("Not implemented for tests");
} }
void Kotlin_Internal_GC_GCInfoBuilder_setPostGcCleanupTime(KRef thiz, KLong value) { void Kotlin_Internal_GC_GCInfoBuilder_setPostGcCleanupTime(KRef thiz, KLong value) {