[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:
committed by
Space Team
parent
3013e3549c
commit
0d04e170b1
@@ -18,6 +18,7 @@ using namespace kotlin;
|
||||
namespace {
|
||||
|
||||
std::atomic<ObjHeader* (*)(ObjHeader*)> weakRefBarrier = nullptr;
|
||||
std::atomic<int64_t> weakProcessingEpoch = 0;
|
||||
|
||||
ObjHeader* weakRefBarrierImpl(ObjHeader* weakReferee) noexcept {
|
||||
if (!weakReferee) return nullptr;
|
||||
@@ -36,47 +37,62 @@ NO_INLINE ObjHeader* weakRefReadSlowPath(std::atomic<ObjHeader*>& weakReferee) n
|
||||
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
|
||||
|
||||
void gc::BarriersThreadData::onCheckpoint() noexcept {
|
||||
visitedCheckpoint_.store(true, std::memory_order_release);
|
||||
void gc::BarriersThreadData::onThreadRegistration() noexcept {
|
||||
if (weakRefBarrier.load(std::memory_order_acquire) != nullptr) {
|
||||
startMarkingNewObjects(GCHandle::getByEpoch(weakProcessingEpoch.load(std::memory_order_relaxed)));
|
||||
}
|
||||
}
|
||||
|
||||
void gc::BarriersThreadData::resetCheckpoint() noexcept {
|
||||
visitedCheckpoint_.store(false, std::memory_order_release);
|
||||
ALWAYS_INLINE void gc::BarriersThreadData::onSafePoint() noexcept {}
|
||||
|
||||
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 {
|
||||
return visitedCheckpoint_.load(std::memory_order_acquire);
|
||||
void gc::BarriersThreadData::stopMarkingNewObjects() noexcept {
|
||||
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);
|
||||
for (auto& mutator: mutators) {
|
||||
mutator.gc().impl().gc().barriers().startMarkingNewObjects(GCHandle::getByEpoch(epoch));
|
||||
}
|
||||
}
|
||||
|
||||
void gc::DisableWeakRefBarriers() noexcept {
|
||||
auto mutators = mm::ThreadRegistry::Instance().LockForIter();
|
||||
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 {
|
||||
// TODO: Make this work with GCs that can stop thread at any point.
|
||||
|
||||
OBJ_GETTER(gc::WeakRefRead, std::atomic<ObjHeader*>& weakReferee) noexcept {
|
||||
if (!compiler::concurrentWeakSweep()) {
|
||||
RETURN_OBJ(weakReferee.load(std::memory_order_relaxed));
|
||||
}
|
||||
|
||||
@@ -10,23 +10,26 @@
|
||||
|
||||
#include "Memory.h"
|
||||
#include "Utils.hpp"
|
||||
#include "GCStatistics.hpp"
|
||||
|
||||
namespace kotlin::gc {
|
||||
|
||||
class BarriersThreadData : private Pinned {
|
||||
public:
|
||||
void onCheckpoint() noexcept;
|
||||
void resetCheckpoint() noexcept;
|
||||
bool visitedCheckpoint() const noexcept;
|
||||
void onThreadRegistration() noexcept;
|
||||
void onSafePoint() noexcept;
|
||||
|
||||
void startMarkingNewObjects(GCHandle gcHandle) noexcept;
|
||||
void stopMarkingNewObjects() noexcept;
|
||||
bool shouldMarkNewObjects() const noexcept;
|
||||
|
||||
void onAllocation(ObjHeader* allocated);
|
||||
private:
|
||||
std::atomic<bool> visitedCheckpoint_ = false;
|
||||
std::optional<GCHandle::GCMarkScope> markHandle_{};
|
||||
};
|
||||
|
||||
// Must be called during STW.
|
||||
void EnableWeakRefBarriers() noexcept;
|
||||
|
||||
// Must be called outside STW.
|
||||
void EnableWeakRefBarriers(int64_t epoch) noexcept;
|
||||
void DisableWeakRefBarriers() 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");
|
||||
gcHandle.suspensionRequested();
|
||||
|
||||
// TODO (WaitForThreadsReadyToMark())
|
||||
RuntimeAssert(!kotlin::mm::IsCurrentThreadRegistered(), "GC must run on unregistered thread");
|
||||
|
||||
markDispatcher_.waitForThreadsPauseMutation();
|
||||
GCLogDebug(epoch, "All threads have paused mutation");
|
||||
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_;
|
||||
scheduler.onGCStart();
|
||||
|
||||
@@ -201,17 +190,8 @@ void gc::ConcurrentMarkAndSweep::PerformFullGC(int64_t epoch) noexcept {
|
||||
|
||||
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()) {
|
||||
// Expected to happen inside STW.
|
||||
gc::EnableWeakRefBarriers();
|
||||
EnableWeakRefBarriers(epoch);
|
||||
|
||||
mm::ResumeThreads();
|
||||
gcHandle.threadsAreResumed();
|
||||
@@ -220,13 +200,42 @@ void gc::ConcurrentMarkAndSweep::PerformFullGC(int64_t epoch) noexcept {
|
||||
gc::processWeaks<DefaultProcessWeaksTraits>(gcHandle, mm::SpecialRefRegistry::instance());
|
||||
|
||||
if (compiler::concurrentWeakSweep()) {
|
||||
// Expected to happen outside STW.
|
||||
gc::DisableWeakRefBarriers();
|
||||
} else {
|
||||
mm::ResumeThreads();
|
||||
gcHandle.threadsAreResumed();
|
||||
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();
|
||||
}
|
||||
|
||||
// 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
|
||||
gc::SweepExtraObjects<DefaultSweepTraits<ObjectFactory>>(gcHandle, *extraObjectFactoryIterable);
|
||||
extraObjectFactoryIterable = std::nullopt;
|
||||
|
||||
@@ -38,7 +38,9 @@ public:
|
||||
|
||||
void OnSuspendForGC() noexcept;
|
||||
|
||||
void safePoint() noexcept { barriers_.onCheckpoint(); }
|
||||
void safePoint() noexcept { barriers_.onSafePoint(); }
|
||||
|
||||
void onThreadRegistration() noexcept { barriers_.onThreadRegistration(); }
|
||||
|
||||
BarriersThreadData& barriers() noexcept { return barriers_; }
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "ObjectTestSupport.hpp"
|
||||
#include "SafePoint.hpp"
|
||||
#include "SingleThreadExecutor.hpp"
|
||||
#include "StableRef.hpp"
|
||||
#include "TestSupport.hpp"
|
||||
#include "ThreadData.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) {
|
||||
std_support::vector<Mutator> mutators(kDefaultThreadCount);
|
||||
@@ -1104,31 +1150,6 @@ TEST_P(ConcurrentMarkAndSweepTest, NewThreadsWhileRequestingCollection) {
|
||||
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;
|
||||
for (int i = 0; i < kDefaultThreadCount; ++i) {
|
||||
expectedAlive.push_back(globals[i]);
|
||||
@@ -1140,9 +1161,27 @@ TEST_P(ConcurrentMarkAndSweepTest, NewThreadsWhileRequestingCollection) {
|
||||
// Unreachables for new threads were not collected.
|
||||
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));
|
||||
#endif // CUSTOM_ALLOCATOR
|
||||
}
|
||||
|
||||
TEST_P(ConcurrentMarkAndSweepTest, FreeObjectWithFreeWeakReversedOrder) {
|
||||
|
||||
@@ -19,7 +19,7 @@ gc::GC::ThreadData::ThreadData(GC& gc, mm::ThreadData& threadData) noexcept : im
|
||||
|
||||
gc::GC::ThreadData::~ThreadData() = default;
|
||||
|
||||
void gc::GC::ThreadData::Publish() noexcept {
|
||||
void gc::GC::ThreadData::PublishObjectFactory() noexcept {
|
||||
#ifndef CUSTOM_ALLOCATOR
|
||||
impl_->extraObjectDataFactoryThreadQueue().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 {
|
||||
ObjHeader* obj;
|
||||
#ifndef CUSTOM_ALLOCATOR
|
||||
return impl_->objectFactoryThreadQueue().CreateObject(typeInfo);
|
||||
obj = impl_->objectFactoryThreadQueue().CreateObject(typeInfo);
|
||||
#else
|
||||
return impl_->alloc().CreateObject(typeInfo);
|
||||
obj = impl_->alloc().CreateObject(typeInfo);
|
||||
#endif
|
||||
impl().gc().barriers().onAllocation(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE ArrayHeader* gc::GC::ThreadData::CreateArray(const TypeInfo* typeInfo, uint32_t elements) noexcept {
|
||||
ArrayHeader* arr;
|
||||
#ifndef CUSTOM_ALLOCATOR
|
||||
return impl_->objectFactoryThreadQueue().CreateArray(typeInfo, elements);
|
||||
arr = impl_->objectFactoryThreadQueue().CreateArray(typeInfo, elements);
|
||||
#else
|
||||
return impl_->alloc().CreateArray(typeInfo, elements);
|
||||
arr = impl_->alloc().CreateArray(typeInfo, elements);
|
||||
#endif
|
||||
impl().gc().barriers().onAllocation(arr->obj());
|
||||
return arr;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE mm::ExtraObjectData& gc::GC::ThreadData::CreateExtraObjectDataForObject(
|
||||
@@ -76,6 +82,10 @@ void gc::GC::ThreadData::safePoint() noexcept {
|
||||
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() = default;
|
||||
|
||||
@@ -169,7 +169,7 @@ private:
|
||||
GCHandle gcHandle_ = GCHandle::invalid();
|
||||
MarkPacer pacer_;
|
||||
std::optional<mm::ThreadRegistry::Iterable> lockedMutatorsList_;
|
||||
ManuallyScoped<ParallelProcessor> parallelProcessor_;
|
||||
ManuallyScoped<ParallelProcessor> parallelProcessor_{};
|
||||
|
||||
std::mutex workerCreationMutex_;
|
||||
std::atomic<std::size_t> activeWorkersCount_ = 0;
|
||||
|
||||
@@ -35,7 +35,7 @@ public:
|
||||
|
||||
Impl& impl() noexcept { return *impl_; }
|
||||
|
||||
void Publish() noexcept;
|
||||
void PublishObjectFactory() noexcept;
|
||||
void ClearForTests() noexcept;
|
||||
|
||||
ObjHeader* CreateObject(const TypeInfo* typeInfo) noexcept;
|
||||
@@ -47,6 +47,8 @@ public:
|
||||
|
||||
void safePoint() noexcept;
|
||||
|
||||
void onThreadRegistration() noexcept;
|
||||
|
||||
private:
|
||||
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_setStartTime(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_setPauseEndTime(KRef thiz, KLong value);
|
||||
void Kotlin_Internal_GC_GCInfoBuilder_setFirstPauseRequestTime(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_setRootSet(KRef thiz,
|
||||
KLong threadLocalReferences, KLong stackReferences,
|
||||
@@ -77,8 +81,15 @@ struct GCInfo {
|
||||
std::optional<uint64_t> epoch;
|
||||
std::optional<KLong> startTime; // time since process start
|
||||
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<RootSetStatistics> rootSet;
|
||||
std::optional<kotlin::gc::MarkStats> markStats;
|
||||
@@ -91,8 +102,12 @@ struct GCInfo {
|
||||
Kotlin_Internal_GC_GCInfoBuilder_setEpoch(builder, static_cast<KLong>(*epoch));
|
||||
if (startTime) Kotlin_Internal_GC_GCInfoBuilder_setStartTime(builder, *startTime);
|
||||
if (endTime) Kotlin_Internal_GC_GCInfoBuilder_setEndTime(builder, *endTime);
|
||||
if (pauseStartTime) Kotlin_Internal_GC_GCInfoBuilder_setPauseStartTime(builder, *pauseStartTime);
|
||||
if (pauseEndTime) Kotlin_Internal_GC_GCInfoBuilder_setPauseEndTime(builder, *pauseEndTime);
|
||||
if (firstPauseRequestTime) Kotlin_Internal_GC_GCInfoBuilder_setFirstPauseRequestTime(builder, *firstPauseRequestTime);
|
||||
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 (rootSet)
|
||||
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,
|
||||
stat->memoryUsageAfter.heap->sizeBytes);
|
||||
}
|
||||
if (stat->pauseStartTime && stat->pauseEndTime) {
|
||||
auto time = (*stat->pauseEndTime - *stat->pauseStartTime) / 1000;
|
||||
GCLogInfo(epoch_, "Mutators pause time: %" PRIu64 " microseconds.", time);
|
||||
if (stat->firstPauseRequestTime && stat->firstPauseStartTime) {
|
||||
auto time = (*stat->firstPauseStartTime - *stat->firstPauseRequestTime) / 1000;
|
||||
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) {
|
||||
auto time = (*current.endTime - *current.startTime) / 1000;
|
||||
@@ -242,14 +269,30 @@ void GCHandle::suspensionRequested() {
|
||||
std::lock_guard guard(lock);
|
||||
GCLogDebug(epoch_, "Requested thread suspension");
|
||||
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() {
|
||||
std::lock_guard guard(lock);
|
||||
if (auto* stat = statByEpoch(epoch_)) {
|
||||
if (stat->pauseStartTime) {
|
||||
auto time = (konan::getTimeNanos() - *stat->pauseStartTime) / 1000;
|
||||
auto startTime = static_cast<KLong>(konan::getTimeNanos());
|
||||
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);
|
||||
return;
|
||||
}
|
||||
@@ -267,9 +310,18 @@ void GCHandle::threadsAreSuspended() {
|
||||
void GCHandle::threadsAreResumed() {
|
||||
std::lock_guard guard(lock);
|
||||
if (auto* stat = statByEpoch(epoch_)) {
|
||||
stat->pauseEndTime = static_cast<KLong>(konan::getTimeNanos());
|
||||
if (stat->pauseStartTime) {
|
||||
auto time = (*stat->pauseEndTime - *stat->pauseStartTime) / 1000;
|
||||
auto endTime = static_cast<KLong>(konan::getTimeNanos());
|
||||
std::optional<KLong> startTime;
|
||||
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);
|
||||
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() {
|
||||
@@ -397,11 +451,27 @@ GCHandle::GCThreadRootSetScope::~GCThreadRootSetScope(){
|
||||
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() {
|
||||
handle_.marked(stats_);
|
||||
GCLogDebug(handle_.getEpoch(), "Marked %" PRIu64 " objects in %" PRIu64 " microseconds.", stats_.markedCount, getStageTime());
|
||||
if (handle_.isValid()) {
|
||||
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) {}
|
||||
|
||||
@@ -95,16 +95,7 @@ public:
|
||||
void addThreadLocalRoot() { threadLocalRoots_++; }
|
||||
};
|
||||
|
||||
class GCMarkScope : GCStageScopeUsTimer, Pinned {
|
||||
GCHandle& handle_;
|
||||
MarkStats stats_;
|
||||
|
||||
public:
|
||||
explicit GCMarkScope(GCHandle& handle);
|
||||
~GCMarkScope();
|
||||
|
||||
void addObject() noexcept { ++stats_.markedCount; }
|
||||
};
|
||||
class GCMarkScope;
|
||||
|
||||
class GCProcessWeaksScope : GCStageScopeUsTimer, Pinned {
|
||||
GCHandle& handle_;
|
||||
@@ -151,9 +142,24 @@ public:
|
||||
GCSweepExtraObjectsScope sweepExtraObjects() { return GCSweepExtraObjectsScope(*this); }
|
||||
GCGlobalRootSetScope collectGlobalRoots() { return GCGlobalRootSetScope(*this); }
|
||||
GCThreadRootSetScope collectThreadRoots(mm::ThreadData& threadData) { return GCThreadRootSetScope(*this, threadData); }
|
||||
GCMarkScope mark() { return GCMarkScope(*this); }
|
||||
GCMarkScope mark();
|
||||
GCProcessWeaksScope processWeaks() noexcept { return GCProcessWeaksScope(*this); }
|
||||
|
||||
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;
|
||||
|
||||
void gc::GC::ThreadData::Publish() noexcept {
|
||||
void gc::GC::ThreadData::PublishObjectFactory() noexcept {
|
||||
#ifndef CUSTOM_ALLOCATOR
|
||||
impl_->extraObjectDataFactoryThreadQueue().Publish();
|
||||
impl_->objectFactoryThreadQueue().Publish();
|
||||
@@ -73,6 +73,8 @@ void gc::GC::ThreadData::OnSuspendForGC() 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() = default;
|
||||
|
||||
@@ -20,7 +20,7 @@ gc::GC::ThreadData::ThreadData(GC& gc, mm::ThreadData& threadData) noexcept : im
|
||||
|
||||
gc::GC::ThreadData::~ThreadData() = default;
|
||||
|
||||
void gc::GC::ThreadData::Publish() noexcept {
|
||||
void gc::GC::ThreadData::PublishObjectFactory() noexcept {
|
||||
#ifndef CUSTOM_ALLOCATOR
|
||||
impl_->extraObjectDataFactoryThreadQueue().Publish();
|
||||
impl_->objectFactoryThreadQueue().Publish();
|
||||
@@ -73,6 +73,8 @@ void gc::GC::ThreadData::OnSuspendForGC() 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() = default;
|
||||
|
||||
@@ -99,6 +99,10 @@ void gc::SameThreadMarkAndSweep::PerformFullGC(int64_t epoch) noexcept {
|
||||
gc::processWeaks<DefaultProcessWeaksTraits>(gcHandle, mm::SpecialRefRegistry::instance());
|
||||
|
||||
#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
|
||||
// would not publish into the global state at an unexpected time.
|
||||
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) {
|
||||
std_support::vector<Mutator> mutators(kDefaultThreadCount);
|
||||
std_support::vector<ObjHeader*> globals(2 * kDefaultThreadCount);
|
||||
@@ -1079,31 +1125,6 @@ TEST_F(SameThreadMarkAndSweepTest, NewThreadsWhileRequestingCollection) {
|
||||
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;
|
||||
for (int i = 0; i < kDefaultThreadCount; ++i) {
|
||||
expectedAlive.push_back(globals[i]);
|
||||
@@ -1115,9 +1136,27 @@ TEST_F(SameThreadMarkAndSweepTest, NewThreadsWhileRequestingCollection) {
|
||||
// Unreachables for new threads were not collected.
|
||||
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));
|
||||
#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 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.
|
||||
* @property pauseStartTimeNs Time, when mutator threads are suspended, mesured by [kotlin.system.getTimeNanos].
|
||||
* @property pauseEndTimeNs Time, when mutator threads are unsuspended, mesured by [kotlin.system.getTimeNanos].
|
||||
* @property firstPauseRequestTimeNs Time, when the garbage collector thread requested suspension of mutator threads for the first time,
|
||||
* 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].
|
||||
* 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 memoryUsageAfter Memory usage at the start of garbage collector run, separated by memory pools.
|
||||
* The set of memory pools depends on the collector implementation.
|
||||
@@ -74,8 +80,12 @@ public class GCInfo(
|
||||
val epoch: Long,
|
||||
val startTimeNs: Long,
|
||||
val endTimeNs: Long,
|
||||
val pauseStartTimeNs: Long,
|
||||
val pauseEndTimeNs: Long,
|
||||
val firstPauseRequestTimeNs: Long,
|
||||
val firstPauseStartTimeNs: Long,
|
||||
val firstPauseEndTimeNs: Long,
|
||||
val secondPauseRequestTimeNs: Long?,
|
||||
val secondPauseStartTimeNs: Long?,
|
||||
val secondPauseEndTimeNs: Long?,
|
||||
val postGcCleanupTimeNs: Long?,
|
||||
val rootSet: RootSetStatistics,
|
||||
val memoryUsageBefore: Map<String, MemoryUsage>,
|
||||
@@ -89,8 +99,12 @@ public class GCInfo(
|
||||
info.epoch,
|
||||
info.startTimeNs,
|
||||
info.endTimeNs,
|
||||
info.pauseStartTimeNs,
|
||||
info.pauseEndTimeNs,
|
||||
info.firstPauseRequestTimeNs,
|
||||
info.firstPauseStartTimeNs,
|
||||
info.firstPauseEndTimeNs,
|
||||
info.secondPauseRequestTimeNs,
|
||||
info.secondPauseStartTimeNs,
|
||||
info.secondPauseEndTimeNs,
|
||||
info.postGcCleanupTimeNs,
|
||||
info.rootSet.let {
|
||||
RootSetStatistics(
|
||||
|
||||
@@ -71,8 +71,14 @@ public class RootSetStatistics(
|
||||
* @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].
|
||||
* 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 pauseEndTimeNs Time, when mutator threads are unsuspended, mesured by [kotlin.system.getTimeNanos].
|
||||
* @property firstPauseRequestTimeNs Time, when the garbage collector thread requested suspension of mutator threads for the first time,
|
||||
* 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].
|
||||
* If null, memory reclamation is still in progress.
|
||||
* @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 startTimeNs: Long,
|
||||
val endTimeNs: Long,
|
||||
val pauseStartTimeNs: Long,
|
||||
val pauseEndTimeNs: Long,
|
||||
val firstPauseRequestTimeNs: Long,
|
||||
val firstPauseStartTimeNs: Long,
|
||||
val firstPauseEndTimeNs: Long,
|
||||
val secondPauseRequestTimeNs: Long?,
|
||||
val secondPauseStartTimeNs: Long?,
|
||||
val secondPauseEndTimeNs: Long?,
|
||||
val postGcCleanupTimeNs: Long?,
|
||||
val rootSet: RootSetStatistics,
|
||||
val markedCount: Long,
|
||||
@@ -116,8 +126,12 @@ private class GCInfoBuilder() {
|
||||
var epoch: Long? = null
|
||||
var startTimeNs: Long? = null
|
||||
var endTimeNs: Long? = null
|
||||
var pauseStartTimeNs: Long? = null
|
||||
var pauseEndTimeNs: Long? = null
|
||||
var firstPauseRequestTimeNs: 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 rootSet: RootSetStatistics? = null
|
||||
var markedCount: Long? = null
|
||||
@@ -140,14 +154,34 @@ private class GCInfoBuilder() {
|
||||
endTimeNs = value
|
||||
}
|
||||
|
||||
@ExportForCppRuntime("Kotlin_Internal_GC_GCInfoBuilder_setPauseStartTime")
|
||||
private fun setPauseStartTime(value: Long) {
|
||||
pauseStartTimeNs = value
|
||||
@ExportForCppRuntime("Kotlin_Internal_GC_GCInfoBuilder_setFirstPauseRequestTime")
|
||||
private fun setFirstPauseRequestTime(value: Long) {
|
||||
firstPauseRequestTimeNs = value
|
||||
}
|
||||
|
||||
@ExportForCppRuntime("Kotlin_Internal_GC_GCInfoBuilder_setPauseEndTime")
|
||||
private fun setPauseEndTime(value: Long) {
|
||||
pauseEndTimeNs = value
|
||||
@ExportForCppRuntime("Kotlin_Internal_GC_GCInfoBuilder_setFirstPauseStartTime")
|
||||
private fun setFirstPauseStartTime(value: Long) {
|
||||
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")
|
||||
@@ -191,8 +225,12 @@ private class GCInfoBuilder() {
|
||||
epoch ?: return null,
|
||||
startTimeNs ?: return null,
|
||||
endTimeNs ?: return null,
|
||||
pauseStartTimeNs ?: return null,
|
||||
pauseEndTimeNs ?: return null,
|
||||
firstPauseRequestTimeNs ?: return null,
|
||||
firstPauseStartTimeNs ?: return null,
|
||||
firstPauseEndTimeNs ?: return null,
|
||||
secondPauseRequestTimeNs,
|
||||
secondPauseStartTimeNs,
|
||||
secondPauseEndTimeNs,
|
||||
postGcCleanupTimeNs,
|
||||
rootSet ?: return null,
|
||||
markedCount ?: return null,
|
||||
|
||||
@@ -78,7 +78,7 @@ TEST_F(ExtraObjectDataTest, ConcurrentInstall) {
|
||||
}
|
||||
auto& extraData = mm::ExtraObjectData::Install(object.header());
|
||||
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_));
|
||||
}
|
||||
|
||||
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")]] WeakRef createWeakRef(ObjHeader* object) noexcept;
|
||||
|
||||
@@ -63,7 +63,6 @@ public:
|
||||
// TODO: These use separate locks, which is inefficient.
|
||||
globalsThreadQueue_.Publish();
|
||||
specialRefRegistry_.publish();
|
||||
gc_.Publish();
|
||||
}
|
||||
|
||||
void ClearForTests() noexcept {
|
||||
|
||||
@@ -18,11 +18,13 @@ mm::ThreadRegistry& mm::ThreadRegistry::Instance() noexcept {
|
||||
}
|
||||
|
||||
mm::ThreadRegistry::Node* mm::ThreadRegistry::RegisterCurrentThread() noexcept {
|
||||
auto lock = list_.LockForIter();
|
||||
auto* threadDataNode = list_.Emplace(konan::currentThreadId());
|
||||
AssertThreadState(threadDataNode->Get(), ThreadState::kNative);
|
||||
Node*& currentDataNode = currentThreadDataNode_;
|
||||
RuntimeAssert(!IsCurrentThreadRegistered(), "This thread already had some data assigned to it.");
|
||||
currentDataNode = threadDataNode;
|
||||
threadDataNode->Get()->gc().onThreadRegistration();
|
||||
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) {
|
||||
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");
|
||||
}
|
||||
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");
|
||||
}
|
||||
void Kotlin_Internal_GC_GCInfoBuilder_setPostGcCleanupTime(KRef thiz, KLong value) {
|
||||
|
||||
Reference in New Issue
Block a user