[K/N] Implement non-blocking approach for sweep
This commit is contained in:
@@ -17,6 +17,8 @@
|
||||
#include "ThreadData.hpp"
|
||||
#include "ThreadRegistry.hpp"
|
||||
#include "ThreadSuspension.hpp"
|
||||
#include "GCState.hpp"
|
||||
#include "FinalizerProcessor.hpp"
|
||||
|
||||
using namespace kotlin;
|
||||
|
||||
@@ -55,13 +57,8 @@ struct SweepTraits {
|
||||
}
|
||||
};
|
||||
|
||||
struct FinalizeTraits {
|
||||
using ObjectFactory = mm::ObjectFactory<gc::ConcurrentMarkAndSweep>;
|
||||
};
|
||||
|
||||
// Global, because it's accessed on a hot path: avoid memory load from `this`.
|
||||
std::atomic<gc::ConcurrentMarkAndSweep::SafepointFlag> gSafepointFlag = gc::ConcurrentMarkAndSweep::SafepointFlag::kNone;
|
||||
|
||||
std::atomic<bool> gNeedSafepointSlowpath = false;
|
||||
} // namespace
|
||||
|
||||
ALWAYS_INLINE void gc::ConcurrentMarkAndSweep::ThreadData::SafePointFunctionPrologue() noexcept {
|
||||
@@ -74,130 +71,141 @@ ALWAYS_INLINE void gc::ConcurrentMarkAndSweep::ThreadData::SafePointLoopBody() n
|
||||
|
||||
void gc::ConcurrentMarkAndSweep::ThreadData::SafePointAllocation(size_t size) noexcept {
|
||||
threadData_.gcScheduler().OnSafePointAllocation(size);
|
||||
SafepointFlag flag = gSafepointFlag.load();
|
||||
if (flag != SafepointFlag::kNone) {
|
||||
SafePointSlowPath(flag);
|
||||
if (gNeedSafepointSlowpath.load()) {
|
||||
SafePointSlowPath();
|
||||
}
|
||||
}
|
||||
void gc::ConcurrentMarkAndSweep::ThreadData::ScheduleAndWaitFullGC() noexcept {
|
||||
ThreadStateGuard guard(ThreadState::kNative);
|
||||
auto scheduled_epoch = gc_.state_.schedule();
|
||||
gc_.state_.waitEpochFinished(scheduled_epoch);
|
||||
}
|
||||
|
||||
void gc::ConcurrentMarkAndSweep::ThreadData::PerformFullGC() noexcept {
|
||||
auto didGC = gc_.PerformFullGC();
|
||||
void gc::ConcurrentMarkAndSweep::ThreadData::ScheduleAndWaitFullGCWithFinalizers() noexcept {
|
||||
ThreadStateGuard guard(ThreadState::kNative);
|
||||
auto scheduled_epoch = gc_.state_.schedule();
|
||||
gc_.state_.waitEpochFinalized(scheduled_epoch);
|
||||
}
|
||||
|
||||
if (!didGC) {
|
||||
// If we failed to suspend threads, someone else might be asking to suspend them.
|
||||
threadData_.suspensionData().suspendIfRequested();
|
||||
}
|
||||
void gc::ConcurrentMarkAndSweep::ThreadData::StopFinalizerThreadForTests() noexcept {
|
||||
gc_.finalizerProcessor_->StopFinalizerThread();
|
||||
}
|
||||
|
||||
void gc::ConcurrentMarkAndSweep::ThreadData::OnOOM(size_t size) noexcept {
|
||||
RuntimeLogDebug({kTagGC}, "Attempt to GC on OOM at size=%zu", size);
|
||||
PerformFullGC();
|
||||
ScheduleAndWaitFullGC();
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void gc::ConcurrentMarkAndSweep::ThreadData::SafePointRegular(size_t weight) noexcept {
|
||||
threadData_.gcScheduler().OnSafePointRegular(weight);
|
||||
SafepointFlag flag = gSafepointFlag.load();
|
||||
if (flag != SafepointFlag::kNone) {
|
||||
SafePointSlowPath(flag);
|
||||
if (gNeedSafepointSlowpath.load()) {
|
||||
SafePointSlowPath();
|
||||
}
|
||||
}
|
||||
|
||||
NO_INLINE void gc::ConcurrentMarkAndSweep::ThreadData::SafePointSlowPath(SafepointFlag flag) noexcept {
|
||||
RuntimeAssert(flag != SafepointFlag::kNone, "Must've been handled by the caller");
|
||||
// No need to check for kNeedsSuspend, because `suspendIfRequested` checks for its own atomic.
|
||||
NO_EXTERNAL_CALLS_CHECK NO_INLINE void gc::ConcurrentMarkAndSweep::ThreadData::SafePointSlowPath() noexcept {
|
||||
threadData_.suspensionData().suspendIfRequested();
|
||||
if (flag == SafepointFlag::kNeedsGC) {
|
||||
RuntimeLogDebug({kTagGC}, "Attempt to GC at SafePoint");
|
||||
PerformFullGC();
|
||||
}
|
||||
}
|
||||
|
||||
gc::ConcurrentMarkAndSweep::ConcurrentMarkAndSweep() noexcept {
|
||||
mm::GlobalData::Instance().gcScheduler().SetScheduleGC([]() {
|
||||
gc::ConcurrentMarkAndSweep::ConcurrentMarkAndSweep() noexcept :
|
||||
finalizerProcessor_(make_unique<FinalizerProcessor>([this](int64_t epoch) { state_.finalized(epoch);})) {
|
||||
mm::GlobalData::Instance().gcScheduler().SetScheduleGC([this]() NO_EXTERNAL_CALLS_CHECK NO_INLINE {
|
||||
RuntimeLogDebug({kTagGC}, "Scheduling GC by thread %d", konan::currentThreadId());
|
||||
gSafepointFlag = SafepointFlag::kNeedsGC;
|
||||
state_.schedule();
|
||||
});
|
||||
gcThread_ = std::thread([this] {
|
||||
while (true) {
|
||||
auto epoch = state_.waitScheduled();
|
||||
if (epoch.has_value()) {
|
||||
PerformFullGC(*epoch);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool gc::ConcurrentMarkAndSweep::PerformFullGC() noexcept {
|
||||
|
||||
gc::ConcurrentMarkAndSweep::~ConcurrentMarkAndSweep() {
|
||||
state_.shutdown();
|
||||
gcThread_.join();
|
||||
}
|
||||
|
||||
|
||||
void gc::ConcurrentMarkAndSweep::RequestThreadsSuspension() noexcept {
|
||||
gNeedSafepointSlowpath = true;
|
||||
bool didSuspend = mm::RequestThreadsSuspension();
|
||||
RuntimeAssert(didSuspend, "Only GC thread can request suspension");
|
||||
}
|
||||
|
||||
void gc::ConcurrentMarkAndSweep::ResumeThreads() noexcept {
|
||||
mm::ResumeThreads();
|
||||
gNeedSafepointSlowpath = false;
|
||||
}
|
||||
|
||||
bool gc::ConcurrentMarkAndSweep::PerformFullGC(int64_t epoch) noexcept {
|
||||
RuntimeLogDebug({kTagGC}, "Attempt to suspend threads by thread %d", konan::currentThreadId());
|
||||
auto timeStartUs = konan::getTimeMicros();
|
||||
bool didSuspend = mm::RequestThreadsSuspension();
|
||||
if (!didSuspend) {
|
||||
RuntimeLogDebug({kTagGC}, "Failed to suspend threads by thread %d", konan::currentThreadId());
|
||||
// Somebody else suspended the threads, and so ran a GC.
|
||||
// TODO: This breaks if suspension is used by something apart from GC.
|
||||
return false;
|
||||
}
|
||||
RequestThreadsSuspension();
|
||||
RuntimeLogDebug({kTagGC}, "Requested thread suspension by thread %d", konan::currentThreadId());
|
||||
gSafepointFlag = SafepointFlag::kNeedsSuspend;
|
||||
|
||||
mm::ObjectFactory<gc::ConcurrentMarkAndSweep>::FinalizerQueue finalizerQueue;
|
||||
{
|
||||
// Switch state to native to simulate this thread being a GC thread.
|
||||
ThreadStateGuard guard(ThreadState::kNative);
|
||||
RuntimeAssert(!kotlin::mm::IsCurrentThreadRegistered(), "Concurrent GC must run on unregistered thread");
|
||||
|
||||
mm::WaitForThreadsSuspension();
|
||||
auto timeSuspendUs = konan::getTimeMicros();
|
||||
RuntimeLogDebug({kTagGC}, "Suspended all threads in %" PRIu64 " microseconds", timeSuspendUs - timeStartUs);
|
||||
mm::WaitForThreadsSuspension();
|
||||
auto timeSuspendUs = konan::getTimeMicros();
|
||||
RuntimeLogDebug({kTagGC}, "Suspended all threads in %" PRIu64 " microseconds", timeSuspendUs - timeStartUs);
|
||||
|
||||
auto& scheduler = mm::GlobalData::Instance().gcScheduler();
|
||||
scheduler.gcData().OnPerformFullGC();
|
||||
auto& scheduler = mm::GlobalData::Instance().gcScheduler();
|
||||
scheduler.gcData().OnPerformFullGC();
|
||||
|
||||
RuntimeLogInfo(
|
||||
{kTagGC}, "Started GC epoch %zu. Time since last GC %" PRIu64 " microseconds", epoch_, timeStartUs - lastGCTimestampUs_);
|
||||
auto graySet = collectRootSet();
|
||||
auto timeRootSetUs = konan::getTimeMicros();
|
||||
// Can be unsafe, because we've stopped the world.
|
||||
auto objectsCountBefore = mm::GlobalData::Instance().objectFactory().GetSizeUnsafe();
|
||||
state_.start(epoch);
|
||||
RuntimeLogInfo(
|
||||
{kTagGC}, "Started GC epoch %" PRId64 ". Time since last GC %" PRIu64 " microseconds", epoch, timeStartUs - lastGCTimestampUs_);
|
||||
auto graySet = collectRootSet();
|
||||
auto timeRootSetUs = konan::getTimeMicros();
|
||||
// Can be unsafe, because we've stopped the world.
|
||||
|
||||
RuntimeLogInfo(
|
||||
{kTagGC}, "Collected root set of size %zu in %" PRIu64 " microseconds", graySet.size(),
|
||||
timeRootSetUs - timeSuspendUs);
|
||||
auto markStats = gc::Mark<MarkTraits>(std::move(graySet));
|
||||
auto timeMarkUs = konan::getTimeMicros();
|
||||
RuntimeLogDebug({kTagGC}, "Marked %zu objects in %" PRIu64 " microseconds. Processed %zu duplicate entries in the gray set", markStats.aliveHeapSet, timeMarkUs - timeRootSetUs, markStats.duplicateEntries);
|
||||
scheduler.gcData().UpdateAliveSetBytes(markStats.aliveHeapSetBytes);
|
||||
gc::SweepExtraObjects<SweepTraits>(mm::GlobalData::Instance().extraObjectDataFactory());
|
||||
auto timeSweepExtraObjectsUs = konan::getTimeMicros();
|
||||
RuntimeLogDebug({kTagGC}, "Sweeped extra objects in %" PRIu64 " microseconds", timeSweepExtraObjectsUs - timeMarkUs);
|
||||
finalizerQueue = gc::Sweep<SweepTraits>(mm::GlobalData::Instance().objectFactory());
|
||||
auto timeSweepUs = konan::getTimeMicros();
|
||||
RuntimeLogDebug({kTagGC}, "Sweeped in %" PRIu64 " microseconds", timeSweepUs - timeSweepExtraObjectsUs);
|
||||
auto objectsCountBefore = mm::GlobalData::Instance().objectFactory().GetSizeUnsafe();
|
||||
RuntimeLogInfo(
|
||||
{kTagGC}, "Collected root set of size %zu in %" PRIu64 " microseconds", graySet.size(),
|
||||
timeRootSetUs - timeSuspendUs);
|
||||
auto markStats = gc::Mark<MarkTraits>(std::move(graySet));
|
||||
auto timeMarkUs = konan::getTimeMicros();
|
||||
RuntimeLogDebug({kTagGC}, "Marked %zu objects in %" PRIu64 " microseconds. Processed %zu duplicate entries in the gray set", markStats.aliveHeapSet, timeMarkUs - timeRootSetUs, markStats.duplicateEntries);
|
||||
scheduler.gcData().UpdateAliveSetBytes(markStats.aliveHeapSetBytes);
|
||||
gc::SweepExtraObjects<SweepTraits>(mm::GlobalData::Instance().extraObjectDataFactory());
|
||||
auto timeSweepExtraObjectsUs = konan::getTimeMicros();
|
||||
RuntimeLogDebug({kTagGC}, "Sweeped extra objects in %" PRIu64 " microseconds", timeSweepExtraObjectsUs - timeMarkUs);
|
||||
|
||||
// Can be unsafe, because we've stopped the world.
|
||||
auto objectsCountAfter = mm::GlobalData::Instance().objectFactory().GetSizeUnsafe();
|
||||
auto extraObjectsCountAfter = mm::GlobalData::Instance().extraObjectDataFactory().GetSizeUnsafe();
|
||||
auto objectFactoryIterable = mm::GlobalData::Instance().objectFactory().LockForIter();
|
||||
|
||||
gSafepointFlag = SafepointFlag::kNone;
|
||||
mm::ResumeThreads();
|
||||
auto timeResumeUs = konan::getTimeMicros();
|
||||
ResumeThreads();
|
||||
auto timeResumeUs = konan::getTimeMicros();
|
||||
|
||||
RuntimeLogDebug({kTagGC}, "Resumed threads in %" PRIu64 " microseconds.", timeResumeUs - timeSweepUs);
|
||||
RuntimeLogDebug({kTagGC},
|
||||
"Resumed threads in %" PRIu64 " microseconds. Total pause for most threads is %" PRIu64" microseconds",
|
||||
timeResumeUs - timeSweepExtraObjectsUs, timeResumeUs - timeStartUs);
|
||||
|
||||
auto finalizersCount = finalizerQueue.size();
|
||||
auto collectedCount = objectsCountBefore - objectsCountAfter - finalizersCount;
|
||||
auto finalizerQueue = gc::Sweep<SweepTraits>(objectFactoryIterable);
|
||||
auto timeSweepUs = konan::getTimeMicros();
|
||||
RuntimeLogDebug({kTagGC}, "Swept in %" PRIu64 " microseconds", timeSweepUs - timeResumeUs);
|
||||
|
||||
RuntimeLogInfo(
|
||||
{kTagGC},
|
||||
"Finished GC epoch %zu. Collected %zu objects, to be finalized %zu objects, %zu objects and %zd extra data objects remain. Total pause time %" PRIu64
|
||||
" microseconds",
|
||||
epoch_, collectedCount, finalizersCount, objectsCountAfter, extraObjectsCountAfter, timeResumeUs - timeStartUs);
|
||||
++epoch_;
|
||||
lastGCTimestampUs_ = timeResumeUs;
|
||||
}
|
||||
// Can be unsafe, because we have a lock in objectFactoryIterable
|
||||
auto objectsCountAfter = mm::GlobalData::Instance().objectFactory().GetSizeUnsafe();
|
||||
auto extraObjectsCountAfter = mm::GlobalData::Instance().extraObjectDataFactory().GetSizeUnsafe();
|
||||
|
||||
// Finalizers are run after threads are resumed, because finalizers may request GC themselves, which would
|
||||
// try to suspend threads again. Also, we run finalizers in the runnable state, because they may be executing
|
||||
// kotlin code.
|
||||
auto finalizersCount = finalizerQueue.size();
|
||||
auto collectedCount = objectsCountBefore - objectsCountAfter - finalizersCount;
|
||||
|
||||
// TODO: These will actually need to be run on a separate thread.
|
||||
AssertThreadState(ThreadState::kRunnable);
|
||||
RuntimeLogDebug({kTagGC}, "Starting to run finalizers");
|
||||
auto timeBeforeUs = konan::getTimeMicros();
|
||||
finalizerQueue.Finalize();
|
||||
auto timeAfterUs = konan::getTimeMicros();
|
||||
RuntimeLogInfo({kTagGC}, "Finished running finalizers in %" PRIu64 " microseconds", timeAfterUs - timeBeforeUs);
|
||||
state_.finish(epoch);
|
||||
finalizerProcessor_->ScheduleTasks(std::move(finalizerQueue), epoch);
|
||||
|
||||
RuntimeLogInfo(
|
||||
{kTagGC},
|
||||
"Finished GC epoch %" PRId64 ". Collected %zu objects, to be finalized %zu objects, %zu objects and %zd extra data objects remain. Total pause time %" PRIu64
|
||||
" microseconds",
|
||||
epoch, collectedCount, finalizersCount, objectsCountAfter, extraObjectsCountAfter, timeSweepUs - timeStartUs);
|
||||
lastGCTimestampUs_ = timeResumeUs;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "ObjectFactory.hpp"
|
||||
#include "Types.h"
|
||||
#include "Utils.hpp"
|
||||
#include "GCState.hpp"
|
||||
|
||||
namespace kotlin {
|
||||
|
||||
@@ -21,14 +22,12 @@ class ThreadData;
|
||||
|
||||
namespace gc {
|
||||
|
||||
// Stop-the-world Mark-and-Sweep that runs on mutator threads. Can support targets that do not have threads.
|
||||
class FinalizerProcessor;
|
||||
|
||||
// Stop-the-world mark + concurrent sweep. The GC runs in a separate thread, finalizers run in another thread of their own.
|
||||
// TODO: Also make mark concurrent.
|
||||
class ConcurrentMarkAndSweep : private Pinned {
|
||||
public:
|
||||
enum class SafepointFlag {
|
||||
kNone,
|
||||
kNeedsSuspend,
|
||||
kNeedsGC,
|
||||
};
|
||||
|
||||
class ObjectData {
|
||||
public:
|
||||
@@ -58,7 +57,9 @@ public:
|
||||
void SafePointExceptionUnwind() noexcept;
|
||||
void SafePointAllocation(size_t size) noexcept;
|
||||
|
||||
void PerformFullGC() noexcept;
|
||||
void ScheduleAndWaitFullGC() noexcept;
|
||||
void ScheduleAndWaitFullGCWithFinalizers() noexcept;
|
||||
void StopFinalizerThreadForTests() noexcept;
|
||||
|
||||
void OnOOM(size_t size) noexcept;
|
||||
|
||||
@@ -66,7 +67,7 @@ public:
|
||||
|
||||
private:
|
||||
void SafePointRegular(size_t weight) noexcept;
|
||||
void SafePointSlowPath(SafepointFlag flag) noexcept;
|
||||
void SafePointSlowPath() noexcept;
|
||||
|
||||
ConcurrentMarkAndSweep& gc_;
|
||||
mm::ThreadData& threadData_;
|
||||
@@ -75,14 +76,18 @@ public:
|
||||
using Allocator = ThreadData::Allocator;
|
||||
|
||||
ConcurrentMarkAndSweep() noexcept;
|
||||
~ConcurrentMarkAndSweep() = default;
|
||||
~ConcurrentMarkAndSweep();
|
||||
|
||||
private:
|
||||
// Returns `true` if GC has happened, and `false` if not (because someone else has suspended the threads).
|
||||
bool PerformFullGC() noexcept;
|
||||
bool PerformFullGC(int64_t epoch) noexcept;
|
||||
void RequestThreadsSuspension() noexcept;
|
||||
void ResumeThreads() noexcept;
|
||||
|
||||
size_t epoch_ = 0;
|
||||
uint64_t lastGCTimestampUs_ = 0;
|
||||
GCStateHolder state_;
|
||||
std::thread gcThread_;
|
||||
KStdUniquePtr<FinalizerProcessor> finalizerProcessor_;
|
||||
};
|
||||
|
||||
} // namespace gc
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
using namespace kotlin;
|
||||
|
||||
// These tests can only work if `GC` is `ConcurrentMarkAndSweep`.
|
||||
// TODO: Extracting GC into a separate module will help with this.
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -251,7 +250,7 @@ TEST_F(ConcurrentMarkAndSweepTest, RootSet) {
|
||||
ASSERT_THAT(GetColor(stack2.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(stack3.header()), Color::kWhite);
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
|
||||
EXPECT_THAT(
|
||||
Alive(threadData),
|
||||
@@ -297,7 +296,7 @@ TEST_F(ConcurrentMarkAndSweepTest, InterconnectedRootSet) {
|
||||
ASSERT_THAT(GetColor(stack2.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(stack3.header()), Color::kWhite);
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
|
||||
EXPECT_THAT(
|
||||
Alive(threadData),
|
||||
@@ -321,7 +320,7 @@ TEST_F(ConcurrentMarkAndSweepTest, FreeObjects) {
|
||||
ASSERT_THAT(GetColor(object1.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object2.header()), Color::kWhite);
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
|
||||
EXPECT_THAT(Alive(threadData), testing::UnorderedElementsAre());
|
||||
});
|
||||
@@ -338,7 +337,8 @@ TEST_F(ConcurrentMarkAndSweepTest, FreeObjectsWithFinalizers) {
|
||||
|
||||
EXPECT_CALL(finalizerHook(), Call(object1.header()));
|
||||
EXPECT_CALL(finalizerHook(), Call(object2.header()));
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGCWithFinalizers();
|
||||
threadData.gc().StopFinalizerThreadForTests();
|
||||
|
||||
EXPECT_THAT(Alive(threadData), testing::UnorderedElementsAre());
|
||||
});
|
||||
@@ -357,7 +357,8 @@ TEST_F(ConcurrentMarkAndSweepTest, FreeObjectWithFreeWeak) {
|
||||
ASSERT_THAT(GetColor(weak1.header()), Color::kWhite);
|
||||
ASSERT_THAT(weak1->referred, object1.header());
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGCWithFinalizers();
|
||||
threadData.gc().StopFinalizerThreadForTests();
|
||||
|
||||
EXPECT_THAT(Alive(threadData), testing::UnorderedElementsAre());
|
||||
});
|
||||
@@ -374,7 +375,7 @@ TEST_F(ConcurrentMarkAndSweepTest, FreeObjectWithHoldedWeak) {
|
||||
ASSERT_THAT(GetColor(weak1.header()), Color::kWhite);
|
||||
ASSERT_THAT(weak1->referred, object1.header());
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
|
||||
EXPECT_THAT(Alive(threadData), testing::UnorderedElementsAre(weak1.header(), stack.header()));
|
||||
EXPECT_THAT(GetColor(weak1.header()), Color::kWhite);
|
||||
@@ -407,7 +408,7 @@ TEST_F(ConcurrentMarkAndSweepTest, ObjectReferencedFromRootSet) {
|
||||
ASSERT_THAT(GetColor(object3.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object4.header()), Color::kWhite);
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
|
||||
EXPECT_THAT(
|
||||
Alive(threadData),
|
||||
@@ -456,7 +457,7 @@ TEST_F(ConcurrentMarkAndSweepTest, ObjectsWithCycles) {
|
||||
ASSERT_THAT(GetColor(object5.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object6.header()), Color::kWhite);
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
|
||||
EXPECT_THAT(
|
||||
Alive(threadData),
|
||||
@@ -507,7 +508,8 @@ TEST_F(ConcurrentMarkAndSweepTest, ObjectsWithCyclesAndFinalizers) {
|
||||
|
||||
EXPECT_CALL(finalizerHook(), Call(object5.header()));
|
||||
EXPECT_CALL(finalizerHook(), Call(object6.header()));
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGCWithFinalizers();
|
||||
threadData.gc().StopFinalizerThreadForTests();
|
||||
|
||||
EXPECT_THAT(
|
||||
Alive(threadData),
|
||||
@@ -540,7 +542,7 @@ TEST_F(ConcurrentMarkAndSweepTest, ObjectsWithCyclesIntoRootSet) {
|
||||
ASSERT_THAT(GetColor(object1.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object2.header()), Color::kWhite);
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
|
||||
EXPECT_THAT(Alive(threadData), testing::UnorderedElementsAre(global.header(), stack.header(), object1.header(), object2.header()));
|
||||
EXPECT_THAT(GetColor(global.header()), Color::kWhite);
|
||||
@@ -584,8 +586,8 @@ TEST_F(ConcurrentMarkAndSweepTest, RunGCTwice) {
|
||||
ASSERT_THAT(GetColor(object5.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object6.header()), Color::kWhite);
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
|
||||
EXPECT_THAT(
|
||||
Alive(threadData),
|
||||
@@ -615,7 +617,7 @@ TEST_F(ConcurrentMarkAndSweepTest, PermanentObjects) {
|
||||
ASSERT_THAT(Alive(threadData), testing::UnorderedElementsAre(global2.header()));
|
||||
EXPECT_THAT(GetColor(global2.header()), Color::kWhite);
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
|
||||
EXPECT_THAT(Alive(threadData), testing::UnorderedElementsAre(global2.header()));
|
||||
EXPECT_THAT(GetColor(global2.header()), Color::kWhite);
|
||||
@@ -635,7 +637,7 @@ TEST_F(ConcurrentMarkAndSweepTest, SameObjectInRootSet) {
|
||||
EXPECT_THAT(GetColor(global.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(object.header()), Color::kWhite);
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
|
||||
EXPECT_THAT(Alive(threadData), testing::UnorderedElementsAre(global.header(), object.header()));
|
||||
EXPECT_THAT(GetColor(global.header()), Color::kWhite);
|
||||
@@ -771,7 +773,7 @@ TEST_F(ConcurrentMarkAndSweepTest, MultipleMutatorsCollect) {
|
||||
|
||||
KStdVector<std::future<void>> gcFutures(kDefaultThreadCount);
|
||||
|
||||
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().PerformFullGC(); });
|
||||
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().ScheduleAndWaitFullGC(); });
|
||||
|
||||
// Spin until thread suspension is requested.
|
||||
while (!mm::IsThreadSuspensionRequested()) {
|
||||
@@ -827,15 +829,27 @@ TEST_F(ConcurrentMarkAndSweepTest, MultipleMutatorsAllCollect) {
|
||||
|
||||
KStdVector<std::future<void>> gcFutures(kDefaultThreadCount);
|
||||
|
||||
// TODO: Maybe check that only one GC is performed.
|
||||
for (int i = 0; i < kDefaultThreadCount; ++i) {
|
||||
gcFutures[i] = mutators[i].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().PerformFullGC(); });
|
||||
gcFutures[i] = mutators[i].Execute([](mm::ThreadData& threadData, Mutator& mutator) {
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
// If GC starts before all thread executed line above, two gc will be run
|
||||
// So we are temporary switch threads to native state and then return them back after all GC runs are done
|
||||
SwitchThreadState(mm::GetMemoryState(), kotlin::ThreadState::kNative);
|
||||
});
|
||||
}
|
||||
|
||||
for (auto& future : gcFutures) {
|
||||
future.wait();
|
||||
}
|
||||
|
||||
for (int i = 0; i < kDefaultThreadCount; ++i) {
|
||||
mutators[i]
|
||||
.Execute([](mm::ThreadData& threadData, Mutator& mutator) {
|
||||
SwitchThreadState(mm::GetMemoryState(), kotlin::ThreadState::kRunnable);
|
||||
})
|
||||
.wait();
|
||||
}
|
||||
|
||||
KStdVector<ObjHeader*> expectedAlive;
|
||||
for (auto& global : globals) {
|
||||
expectedAlive.push_back(global);
|
||||
@@ -891,7 +905,7 @@ TEST_F(ConcurrentMarkAndSweepTest, MultipleMutatorsAddToRootSetAfterCollectionRe
|
||||
}
|
||||
|
||||
KStdVector<std::future<void>> gcFutures(kDefaultThreadCount);
|
||||
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().PerformFullGC(); });
|
||||
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().ScheduleAndWaitFullGC(); });
|
||||
|
||||
// Spin until thread suspension is requested.
|
||||
while (!mm::IsThreadSuspensionRequested()) {
|
||||
@@ -956,7 +970,7 @@ TEST_F(ConcurrentMarkAndSweepTest, CrossThreadReference) {
|
||||
|
||||
KStdVector<std::future<void>> gcFutures(kDefaultThreadCount);
|
||||
|
||||
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().PerformFullGC(); });
|
||||
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().ScheduleAndWaitFullGC(); });
|
||||
|
||||
// Spin until thread suspension is requested.
|
||||
while (!mm::IsThreadSuspensionRequested()) {
|
||||
@@ -1018,7 +1032,7 @@ TEST_F(ConcurrentMarkAndSweepTest, MultipleMutatorsWeaks) {
|
||||
KStdVector<std::future<void>> gcFutures(kDefaultThreadCount);
|
||||
|
||||
gcFutures[0] = mutators[0].Execute([weak](mm::ThreadData& threadData, Mutator& mutator) {
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
EXPECT_THAT((*weak)->referred, nullptr);
|
||||
});
|
||||
|
||||
@@ -1069,7 +1083,7 @@ TEST_F(ConcurrentMarkAndSweepTest, NewThreadsWhileRequestingCollection) {
|
||||
|
||||
KStdVector<std::future<void>> gcFutures(kDefaultThreadCount);
|
||||
|
||||
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().PerformFullGC(); });
|
||||
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().ScheduleAndWaitFullGC(); });
|
||||
|
||||
// Spin until thread suspension is requested.
|
||||
while (!mm::IsThreadSuspensionRequested()) {
|
||||
@@ -1135,7 +1149,7 @@ TEST_F(ConcurrentMarkAndSweepTest, FreeObjectWithFreeWeakReversedOrder) {
|
||||
object1 = &object1_local;
|
||||
global1->field1 = object1_local.header();
|
||||
while (weak.load() == nullptr);
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
|
||||
ASSERT_THAT(Alive(threadData), testing::UnorderedElementsAre(object1_local.header(), weak.load()->header(), global1.header()));
|
||||
ASSERT_THAT(GetColor(global1.header()), Color::kWhite);
|
||||
@@ -1145,7 +1159,7 @@ TEST_F(ConcurrentMarkAndSweepTest, FreeObjectWithFreeWeakReversedOrder) {
|
||||
|
||||
global1->field1 = nullptr;
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
|
||||
EXPECT_THAT(Alive(threadData), testing::UnorderedElementsAre(global1.header()));
|
||||
done = true;
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "FinalizerProcessor.hpp"
|
||||
#include "ObjectFactory.hpp"
|
||||
#include "Runtime.h"
|
||||
|
||||
#include <thread>
|
||||
|
||||
|
||||
void kotlin::gc::FinalizerProcessor::StartFinalizerThreadIfNone() noexcept {
|
||||
if (finalizerThread_.joinable()) return;
|
||||
finalizerThread_ = std::thread([this] {
|
||||
Kotlin_initRuntimeIfNeeded();
|
||||
int64_t finalizersEpoch = 0;
|
||||
while (true) {
|
||||
std::unique_lock lock(finalizerQueueMutex_);
|
||||
finalizerQueueCondVar_.wait(lock, [this, &finalizersEpoch] {
|
||||
return finalizerQueue_.size() > 0 || finalizerQueueEpoch_ != finalizersEpoch || shutdownFlag_;
|
||||
});
|
||||
if (finalizerQueue_.size() == 0 && finalizerQueueEpoch_ == finalizersEpoch) {
|
||||
newTasksAllowed_ = false;
|
||||
RuntimeAssert(shutdownFlag_, "Nothing to do, but no shutdownFlag_ is set on wakeup");
|
||||
break;
|
||||
}
|
||||
auto queue = std::move(finalizerQueue_);
|
||||
finalizersEpoch = finalizerQueueEpoch_;
|
||||
lock.unlock();
|
||||
if (queue.size() > 0) {
|
||||
ThreadStateGuard guard(ThreadState::kRunnable);
|
||||
queue.Finalize();
|
||||
}
|
||||
epochDoneCallback_(finalizersEpoch);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void kotlin::gc::FinalizerProcessor::StopFinalizerThread() noexcept {
|
||||
{
|
||||
std::unique_lock guard(finalizerQueueMutex_);
|
||||
if (!finalizerThread_.joinable()) return;
|
||||
shutdownFlag_ = true;
|
||||
finalizerQueueCondVar_.notify_all();
|
||||
}
|
||||
finalizerThread_.join();
|
||||
shutdownFlag_ = false;
|
||||
RuntimeAssert(finalizerQueue_.size() == 0, "Finalizer queue should be empty when killing finalizer thread");
|
||||
std::unique_lock guard(finalizerQueueMutex_);
|
||||
newTasksAllowed_ = true;
|
||||
finalizerQueueCondVar_.notify_all();
|
||||
}
|
||||
|
||||
void kotlin::gc::FinalizerProcessor::ScheduleTasks(Queue&& tasks, int64_t epoch) noexcept {
|
||||
std::unique_lock guard(finalizerQueueMutex_);
|
||||
if (tasks.size() == 0 && !IsRunning()) {
|
||||
epochDoneCallback_(epoch);
|
||||
return;
|
||||
}
|
||||
finalizerQueueCondVar_.wait(guard, [this] { return newTasksAllowed_; });
|
||||
StartFinalizerThreadIfNone();
|
||||
finalizerQueue_.MergeWith(std::move(tasks));
|
||||
finalizerQueueEpoch_ = epoch;
|
||||
finalizerQueueCondVar_.notify_all();
|
||||
}
|
||||
|
||||
bool kotlin::gc::FinalizerProcessor::IsRunning() noexcept {
|
||||
return finalizerThread_.joinable();
|
||||
}
|
||||
|
||||
kotlin::gc::FinalizerProcessor::~FinalizerProcessor() {
|
||||
StopFinalizerThread();
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ObjectFactory.hpp"
|
||||
#include "ConcurrentMarkAndSweep.hpp"
|
||||
#include "GCState.hpp"
|
||||
|
||||
namespace kotlin::gc {
|
||||
class FinalizerProcessor : Pinned {
|
||||
public:
|
||||
using Queue = typename kotlin::mm::ObjectFactory<ConcurrentMarkAndSweep>::FinalizerQueue;
|
||||
// epochDoneCallback could be called on any subset of them.
|
||||
// If no new tasks are set, epochDoneCallback will be eventually called on last epoch
|
||||
explicit FinalizerProcessor(std::function<void(int64_t)> epochDoneCallback): epochDoneCallback_(std::move(epochDoneCallback)) {}
|
||||
void ScheduleTasks(Queue&& tasks, int64_t epoch) noexcept;
|
||||
void StopFinalizerThread() noexcept;
|
||||
bool IsRunning() noexcept;
|
||||
~FinalizerProcessor();
|
||||
private:
|
||||
void StartFinalizerThreadIfNone() noexcept;
|
||||
std::thread finalizerThread_;
|
||||
Queue finalizerQueue_;
|
||||
std::condition_variable finalizerQueueCondVar_;
|
||||
std::mutex finalizerQueueMutex_;
|
||||
std::function<void(int64_t)> epochDoneCallback_;
|
||||
int64_t finalizerQueueEpoch_ = 0;
|
||||
bool shutdownFlag_ = false;
|
||||
bool newTasksAllowed_ = true;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "ConcurrentMarkAndSweep.hpp"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "ExtraObjectData.hpp"
|
||||
#include "FinalizerHooksTestSupport.hpp"
|
||||
#include "GlobalData.hpp"
|
||||
#include "ObjectOps.hpp"
|
||||
#include "ObjectTestSupport.hpp"
|
||||
#include "TestSupport.hpp"
|
||||
#include "ThreadData.hpp"
|
||||
#include "FinalizerProcessor.hpp"
|
||||
|
||||
using namespace kotlin;
|
||||
|
||||
// These tests can only work if `GC` is `ConcurrentMarkAndSweep`.
|
||||
|
||||
namespace {
|
||||
|
||||
struct Payload {
|
||||
ObjHeader* field1;
|
||||
ObjHeader* field2;
|
||||
ObjHeader* field3;
|
||||
|
||||
static constexpr std::array kFields = {
|
||||
&Payload::field1,
|
||||
&Payload::field2,
|
||||
&Payload::field3,
|
||||
};
|
||||
};
|
||||
|
||||
test_support::TypeInfoHolder typeHolder{test_support::TypeInfoHolder::ObjectBuilder<Payload>()};
|
||||
test_support::TypeInfoHolder typeHolderWithFinalizer{test_support::TypeInfoHolder::ObjectBuilder<Payload>().addFlag(TF_HAS_FINALIZER)};
|
||||
|
||||
test_support::Object<Payload>& AllocateObjectWithFinalizer(mm::ThreadData& threadData) {
|
||||
ObjHolder holder;
|
||||
mm::AllocateObject(&threadData, typeHolderWithFinalizer.typeInfo(), holder.slot());
|
||||
return test_support::Object<Payload>::FromObjHeader(holder.obj());
|
||||
}
|
||||
|
||||
class FinalizerProcessorTest : public testing::Test {
|
||||
public:
|
||||
|
||||
~FinalizerProcessorTest() {
|
||||
mm::GlobalsRegistry::Instance().ClearForTests();
|
||||
mm::GlobalData::Instance().extraObjectDataFactory().ClearForTests();
|
||||
mm::GlobalData::Instance().objectFactory().ClearForTests();
|
||||
}
|
||||
|
||||
testing::MockFunction<void(ObjHeader*)>& finalizerHook() { return finalizerHooks_.finalizerHook(); }
|
||||
|
||||
private:
|
||||
FinalizerHooksTestSupport finalizerHooks_;
|
||||
};
|
||||
|
||||
int threadsCount() {
|
||||
int result = 0;
|
||||
for (auto &thread: mm::ThreadRegistry::Instance().LockForIter()) {
|
||||
static_cast<void>(thread); // to avoid unused warning
|
||||
result++;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_F(FinalizerProcessorTest, NotRunningThreadWhenUnused) {
|
||||
GCStateHolder state;
|
||||
gc::FinalizerProcessor processor([](int64_t) {});
|
||||
ASSERT_EQ(threadsCount(), 0);
|
||||
ASSERT_FALSE(processor.IsRunning());
|
||||
mm::ObjectFactory<kotlin::gc::ConcurrentMarkAndSweep>::FinalizerQueue queue;
|
||||
processor.ScheduleTasks(std::move(queue), 1);
|
||||
ASSERT_EQ(threadsCount(), 0);
|
||||
ASSERT_FALSE(processor.IsRunning());
|
||||
}
|
||||
|
||||
TEST_F(FinalizerProcessorTest, RemoveObject) {
|
||||
RunInNewThread([this] {
|
||||
ASSERT_EQ(threadsCount(), 1);
|
||||
std::atomic<int64_t> done = 0;
|
||||
gc::FinalizerProcessor processor([&](int64_t epoch) { done = epoch; });
|
||||
mm::ObjectFactory<kotlin::gc::ConcurrentMarkAndSweep>::FinalizerQueue queue;
|
||||
auto &object = AllocateObjectWithFinalizer(*mm::ThreadRegistry::Instance().CurrentThreadData());
|
||||
mm::ThreadRegistry::Instance().CurrentThreadData()->Publish();
|
||||
auto &factory = mm::GlobalData::Instance().objectFactory();
|
||||
auto iter = factory.LockForIter();
|
||||
auto iterator = iter.begin();
|
||||
iter.MoveAndAdvance(queue, iterator);
|
||||
ASSERT_EQ(queue.size(), 1u);
|
||||
EXPECT_CALL(finalizerHook(), Call(object.header()));
|
||||
processor.ScheduleTasks(std::move(queue), 1);
|
||||
while (done != 1) {}
|
||||
ASSERT_EQ(threadsCount(), 2);
|
||||
ASSERT_TRUE(processor.IsRunning());
|
||||
processor.StopFinalizerThread();
|
||||
ASSERT_EQ(threadsCount(), 1);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(FinalizerProcessorTest, ScheduleTasksWhileFinalizing) {
|
||||
RunInNewThread([this] {
|
||||
std::atomic<int64_t> done = 0;
|
||||
gc::FinalizerProcessor processor([&done](int64_t epoch) { done = epoch; });
|
||||
std::vector<mm::ObjectFactory<kotlin::gc::ConcurrentMarkAndSweep>::FinalizerQueue> queues;
|
||||
int epochs = 100;
|
||||
std::vector<ObjHeader*> headers;
|
||||
for (int epoch = 0; epoch < epochs; epoch++) {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
auto& object = AllocateObjectWithFinalizer(*mm::ThreadRegistry::Instance().CurrentThreadData());
|
||||
headers.push_back(object.header());
|
||||
}
|
||||
auto& factory = mm::GlobalData::Instance().objectFactory();
|
||||
mm::ThreadRegistry::Instance().CurrentThreadData()->Publish();
|
||||
auto iter = factory.LockForIter();
|
||||
mm::ObjectFactory<kotlin::gc::ConcurrentMarkAndSweep>::FinalizerQueue queue;
|
||||
for (auto iterator = iter.begin(); iterator != iter.end();) {
|
||||
iter.MoveAndAdvance(queue, iterator);
|
||||
}
|
||||
queues.push_back(std::move(queue));
|
||||
}
|
||||
for (auto header: headers) {
|
||||
EXPECT_CALL(finalizerHook(), Call(header));
|
||||
}
|
||||
for (int epoch = 0; epoch < epochs; epoch++) {
|
||||
processor.ScheduleTasks(std::move(queues[epoch]), epoch + 1);
|
||||
}
|
||||
while (done != epochs) {}
|
||||
ASSERT_EQ(threadsCount(), 2);
|
||||
ASSERT_TRUE(processor.IsRunning());
|
||||
processor.StopFinalizerThread();
|
||||
ASSERT_EQ(threadsCount(), 1);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <optional>
|
||||
|
||||
class GCStateHolder {
|
||||
public:
|
||||
int64_t schedule() {
|
||||
std::unique_lock lock(mutex_);
|
||||
if (scheduledEpoch <= startedEpoch) {
|
||||
scheduledEpoch = startedEpoch + 1;
|
||||
cond_.notify_all();
|
||||
}
|
||||
return scheduledEpoch;
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
std::unique_lock lock(mutex_);
|
||||
shutdownFlag_ = true;
|
||||
cond_.notify_all();
|
||||
}
|
||||
|
||||
void start(int64_t epoch) {
|
||||
std::unique_lock lock(mutex_);
|
||||
startedEpoch = epoch;
|
||||
cond_.notify_all();
|
||||
}
|
||||
|
||||
void finish(int64_t epoch) {
|
||||
std::unique_lock lock(mutex_);
|
||||
finishedEpoch = epoch;
|
||||
cond_.notify_all();
|
||||
}
|
||||
|
||||
void finalized(int64_t epoch) {
|
||||
std::unique_lock lock(mutex_);
|
||||
finalizedEpoch = epoch;
|
||||
cond_.notify_all();
|
||||
}
|
||||
|
||||
void waitEpochFinished(int64_t epoch) {
|
||||
std::unique_lock lock(mutex_);
|
||||
cond_.wait(lock, [this, epoch] { return finishedEpoch >= epoch || shutdownFlag_; });
|
||||
}
|
||||
|
||||
void waitEpochFinalized(int64_t epoch) {
|
||||
std::unique_lock lock(mutex_);
|
||||
cond_.wait(lock, [this, epoch] { return finalizedEpoch >= epoch || shutdownFlag_; });
|
||||
}
|
||||
|
||||
std::optional<int64_t> waitScheduled() {
|
||||
std::unique_lock lock(mutex_);
|
||||
cond_.wait(lock, [this] { return scheduledEpoch > finishedEpoch || shutdownFlag_; });
|
||||
if (shutdownFlag_) return std::nullopt;
|
||||
return scheduledEpoch;
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
std::condition_variable cond_;
|
||||
int64_t startedEpoch = 0;
|
||||
int64_t finishedEpoch = 0;
|
||||
int64_t scheduledEpoch = 0;
|
||||
int64_t finalizedEpoch = 0;
|
||||
bool shutdownFlag_ = false;
|
||||
};
|
||||
@@ -85,26 +85,31 @@ void SweepExtraObjects(typename Traits::ExtraObjectsFactory& objectFactory) noex
|
||||
}
|
||||
|
||||
template <typename Traits>
|
||||
typename Traits::ObjectFactory::FinalizerQueue Sweep(typename Traits::ObjectFactory& objectFactory) noexcept {
|
||||
typename Traits::ObjectFactory::FinalizerQueue Sweep(typename Traits::ObjectFactory::Iterable& objectFactoryIter) noexcept {
|
||||
typename Traits::ObjectFactory::FinalizerQueue finalizerQueue;
|
||||
|
||||
auto iter = objectFactory.LockForIter();
|
||||
for (auto it = iter.begin(); it != iter.end();) {
|
||||
for (auto it = objectFactoryIter.begin(); it != objectFactoryIter.end();) {
|
||||
if (Traits::TryResetMark(*it)) {
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
auto* objHeader = it->IsArray() ? it->GetArrayHeader()->obj() : it->GetObjHeader();
|
||||
if (HasFinalizers(objHeader)) {
|
||||
iter.MoveAndAdvance(finalizerQueue, it);
|
||||
objectFactoryIter.MoveAndAdvance(finalizerQueue, it);
|
||||
} else {
|
||||
iter.EraseAndAdvance(it);
|
||||
objectFactoryIter.EraseAndAdvance(it);
|
||||
}
|
||||
}
|
||||
|
||||
return finalizerQueue;
|
||||
}
|
||||
|
||||
template <typename Traits>
|
||||
typename Traits::ObjectFactory::FinalizerQueue Sweep(typename Traits::ObjectFactory& objectFactory) noexcept {
|
||||
auto iter = objectFactory.LockForIter();
|
||||
return Sweep<Traits>(iter);
|
||||
}
|
||||
|
||||
KStdVector<ObjHeader*> collectRootSet();
|
||||
|
||||
} // namespace gc
|
||||
|
||||
@@ -40,7 +40,8 @@ public:
|
||||
void SafePointLoopBody() noexcept {}
|
||||
void SafePointAllocation(size_t size) noexcept {}
|
||||
|
||||
void PerformFullGC() noexcept {}
|
||||
void ScheduleAndWaitFullGC() noexcept {}
|
||||
void ScheduleAndWaitFullGCWithFinalizers() noexcept {}
|
||||
|
||||
void OnOOM(size_t size) noexcept {}
|
||||
|
||||
@@ -53,6 +54,7 @@ public:
|
||||
~NoOpGC() = default;
|
||||
|
||||
GCScheduler& scheduler() noexcept { return scheduler_; }
|
||||
void StopFinalizerThreadForTests() noexcept {}
|
||||
|
||||
private:
|
||||
GCScheduler scheduler_;
|
||||
|
||||
@@ -80,7 +80,7 @@ void gc::SameThreadMarkAndSweep::ThreadData::SafePointAllocation(size_t size) no
|
||||
}
|
||||
}
|
||||
|
||||
void gc::SameThreadMarkAndSweep::ThreadData::PerformFullGC() noexcept {
|
||||
void gc::SameThreadMarkAndSweep::ThreadData::ScheduleAndWaitFullGC() noexcept {
|
||||
auto didGC = gc_.PerformFullGC();
|
||||
|
||||
if (!didGC) {
|
||||
@@ -91,7 +91,7 @@ void gc::SameThreadMarkAndSweep::ThreadData::PerformFullGC() noexcept {
|
||||
|
||||
void gc::SameThreadMarkAndSweep::ThreadData::OnOOM(size_t size) noexcept {
|
||||
RuntimeLogDebug({kTagGC}, "Attempt to GC on OOM at size=%zu", size);
|
||||
PerformFullGC();
|
||||
ScheduleAndWaitFullGC();
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void gc::SameThreadMarkAndSweep::ThreadData::SafePointRegular(size_t weight) noexcept {
|
||||
@@ -108,7 +108,7 @@ NO_INLINE void gc::SameThreadMarkAndSweep::ThreadData::SafePointSlowPath(Safepoi
|
||||
threadData_.suspensionData().suspendIfRequested();
|
||||
if (flag == SafepointFlag::kNeedsGC) {
|
||||
RuntimeLogDebug({kTagGC}, "Attempt to GC at SafePoint");
|
||||
PerformFullGC();
|
||||
ScheduleAndWaitFullGC();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,8 @@ public:
|
||||
void SafePointExceptionUnwind() noexcept;
|
||||
void SafePointAllocation(size_t size) noexcept;
|
||||
|
||||
void PerformFullGC() noexcept;
|
||||
void ScheduleAndWaitFullGC() noexcept;
|
||||
void ScheduleAndWaitFullGCWithFinalizers() noexcept { ScheduleAndWaitFullGC(); }
|
||||
|
||||
void OnOOM(size_t size) noexcept;
|
||||
|
||||
@@ -77,6 +78,7 @@ public:
|
||||
|
||||
SameThreadMarkAndSweep() noexcept;
|
||||
~SameThreadMarkAndSweep() = default;
|
||||
void StopFinalizerThreadForTests() noexcept {}
|
||||
|
||||
private:
|
||||
// Returns `true` if GC has happened, and `false` if not (because someone else has suspended the threads).
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
using namespace kotlin;
|
||||
|
||||
// These tests can only work if `GC` is `SameThreadMarkAndSweep`.
|
||||
// TODO: Extracting GC into a separate module will help with this.
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -251,7 +250,7 @@ TEST_F(SameThreadMarkAndSweepTest, RootSet) {
|
||||
ASSERT_THAT(GetColor(stack2.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(stack3.header()), Color::kWhite);
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
|
||||
EXPECT_THAT(
|
||||
Alive(threadData),
|
||||
@@ -297,7 +296,7 @@ TEST_F(SameThreadMarkAndSweepTest, InterconnectedRootSet) {
|
||||
ASSERT_THAT(GetColor(stack2.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(stack3.header()), Color::kWhite);
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
|
||||
EXPECT_THAT(
|
||||
Alive(threadData),
|
||||
@@ -321,7 +320,7 @@ TEST_F(SameThreadMarkAndSweepTest, FreeObjects) {
|
||||
ASSERT_THAT(GetColor(object1.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object2.header()), Color::kWhite);
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
|
||||
EXPECT_THAT(Alive(threadData), testing::UnorderedElementsAre());
|
||||
});
|
||||
@@ -338,7 +337,7 @@ TEST_F(SameThreadMarkAndSweepTest, FreeObjectsWithFinalizers) {
|
||||
|
||||
EXPECT_CALL(finalizerHook(), Call(object1.header()));
|
||||
EXPECT_CALL(finalizerHook(), Call(object2.header()));
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
|
||||
EXPECT_THAT(Alive(threadData), testing::UnorderedElementsAre());
|
||||
});
|
||||
@@ -357,7 +356,7 @@ TEST_F(SameThreadMarkAndSweepTest, FreeObjectWithFreeWeak) {
|
||||
ASSERT_THAT(GetColor(weak1.header()), Color::kWhite);
|
||||
ASSERT_THAT(weak1->referred, object1.header());
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
|
||||
EXPECT_THAT(Alive(threadData), testing::UnorderedElementsAre());
|
||||
});
|
||||
@@ -374,7 +373,7 @@ TEST_F(SameThreadMarkAndSweepTest, FreeObjectWithHoldedWeak) {
|
||||
ASSERT_THAT(GetColor(weak1.header()), Color::kWhite);
|
||||
ASSERT_THAT(weak1->referred, object1.header());
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
|
||||
EXPECT_THAT(Alive(threadData), testing::UnorderedElementsAre(weak1.header(), stack.header()));
|
||||
EXPECT_THAT(GetColor(weak1.header()), Color::kWhite);
|
||||
@@ -407,7 +406,7 @@ TEST_F(SameThreadMarkAndSweepTest, ObjectReferencedFromRootSet) {
|
||||
ASSERT_THAT(GetColor(object3.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object4.header()), Color::kWhite);
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
|
||||
EXPECT_THAT(
|
||||
Alive(threadData),
|
||||
@@ -456,7 +455,7 @@ TEST_F(SameThreadMarkAndSweepTest, ObjectsWithCycles) {
|
||||
ASSERT_THAT(GetColor(object5.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object6.header()), Color::kWhite);
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
|
||||
EXPECT_THAT(
|
||||
Alive(threadData),
|
||||
@@ -507,7 +506,7 @@ TEST_F(SameThreadMarkAndSweepTest, ObjectsWithCyclesAndFinalizers) {
|
||||
|
||||
EXPECT_CALL(finalizerHook(), Call(object5.header()));
|
||||
EXPECT_CALL(finalizerHook(), Call(object6.header()));
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
|
||||
EXPECT_THAT(
|
||||
Alive(threadData),
|
||||
@@ -540,7 +539,7 @@ TEST_F(SameThreadMarkAndSweepTest, ObjectsWithCyclesIntoRootSet) {
|
||||
ASSERT_THAT(GetColor(object1.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object2.header()), Color::kWhite);
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
|
||||
EXPECT_THAT(Alive(threadData), testing::UnorderedElementsAre(global.header(), stack.header(), object1.header(), object2.header()));
|
||||
EXPECT_THAT(GetColor(global.header()), Color::kWhite);
|
||||
@@ -584,8 +583,8 @@ TEST_F(SameThreadMarkAndSweepTest, RunGCTwice) {
|
||||
ASSERT_THAT(GetColor(object5.header()), Color::kWhite);
|
||||
ASSERT_THAT(GetColor(object6.header()), Color::kWhite);
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
|
||||
EXPECT_THAT(
|
||||
Alive(threadData),
|
||||
@@ -615,7 +614,7 @@ TEST_F(SameThreadMarkAndSweepTest, PermanentObjects) {
|
||||
ASSERT_THAT(Alive(threadData), testing::UnorderedElementsAre(global2.header()));
|
||||
EXPECT_THAT(GetColor(global2.header()), Color::kWhite);
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
|
||||
EXPECT_THAT(Alive(threadData), testing::UnorderedElementsAre(global2.header()));
|
||||
EXPECT_THAT(GetColor(global2.header()), Color::kWhite);
|
||||
@@ -635,7 +634,7 @@ TEST_F(SameThreadMarkAndSweepTest, SameObjectInRootSet) {
|
||||
EXPECT_THAT(GetColor(global.header()), Color::kWhite);
|
||||
EXPECT_THAT(GetColor(object.header()), Color::kWhite);
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
|
||||
EXPECT_THAT(Alive(threadData), testing::UnorderedElementsAre(global.header(), object.header()));
|
||||
EXPECT_THAT(GetColor(global.header()), Color::kWhite);
|
||||
@@ -771,7 +770,7 @@ TEST_F(SameThreadMarkAndSweepTest, MultipleMutatorsCollect) {
|
||||
|
||||
KStdVector<std::future<void>> gcFutures(kDefaultThreadCount);
|
||||
|
||||
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().PerformFullGC(); });
|
||||
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().ScheduleAndWaitFullGC(); });
|
||||
|
||||
// Spin until thread suspension is requested.
|
||||
while (!mm::IsThreadSuspensionRequested()) {
|
||||
@@ -829,7 +828,7 @@ TEST_F(SameThreadMarkAndSweepTest, MultipleMutatorsAllCollect) {
|
||||
|
||||
// TODO: Maybe check that only one GC is performed.
|
||||
for (int i = 0; i < kDefaultThreadCount; ++i) {
|
||||
gcFutures[i] = mutators[i].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().PerformFullGC(); });
|
||||
gcFutures[i] = mutators[i].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().ScheduleAndWaitFullGC(); });
|
||||
}
|
||||
|
||||
for (auto& future : gcFutures) {
|
||||
@@ -891,7 +890,7 @@ TEST_F(SameThreadMarkAndSweepTest, MultipleMutatorsAddToRootSetAfterCollectionRe
|
||||
}
|
||||
|
||||
KStdVector<std::future<void>> gcFutures(kDefaultThreadCount);
|
||||
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().PerformFullGC(); });
|
||||
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().ScheduleAndWaitFullGC(); });
|
||||
|
||||
// Spin until thread suspension is requested.
|
||||
while (!mm::IsThreadSuspensionRequested()) {
|
||||
@@ -956,7 +955,7 @@ TEST_F(SameThreadMarkAndSweepTest, CrossThreadReference) {
|
||||
|
||||
KStdVector<std::future<void>> gcFutures(kDefaultThreadCount);
|
||||
|
||||
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().PerformFullGC(); });
|
||||
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().ScheduleAndWaitFullGC(); });
|
||||
|
||||
// Spin until thread suspension is requested.
|
||||
while (!mm::IsThreadSuspensionRequested()) {
|
||||
@@ -1018,7 +1017,7 @@ TEST_F(SameThreadMarkAndSweepTest, MultipleMutatorsWeaks) {
|
||||
KStdVector<std::future<void>> gcFutures(kDefaultThreadCount);
|
||||
|
||||
gcFutures[0] = mutators[0].Execute([weak](mm::ThreadData& threadData, Mutator& mutator) {
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
EXPECT_THAT((*weak)->referred, nullptr);
|
||||
});
|
||||
|
||||
@@ -1069,7 +1068,7 @@ TEST_F(SameThreadMarkAndSweepTest, NewThreadsWhileRequestingCollection) {
|
||||
|
||||
KStdVector<std::future<void>> gcFutures(kDefaultThreadCount);
|
||||
|
||||
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().PerformFullGC(); });
|
||||
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().ScheduleAndWaitFullGC(); });
|
||||
|
||||
// Spin until thread suspension is requested.
|
||||
while (!mm::IsThreadSuspensionRequested()) {
|
||||
@@ -1135,7 +1134,7 @@ TEST_F(SameThreadMarkAndSweepTest, FreeObjectWithFreeWeakReversedOrder) {
|
||||
object1 = &object1_local;
|
||||
global1->field1 = object1_local.header();
|
||||
while (weak.load() == nullptr);
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
|
||||
ASSERT_THAT(Alive(threadData), testing::UnorderedElementsAre(object1_local.header(), weak.load()->header(), global1.header()));
|
||||
ASSERT_THAT(GetColor(global1.header()), Color::kWhite);
|
||||
@@ -1145,7 +1144,7 @@ TEST_F(SameThreadMarkAndSweepTest, FreeObjectWithFreeWeakReversedOrder) {
|
||||
|
||||
global1->field1 = nullptr;
|
||||
|
||||
threadData.gc().PerformFullGC();
|
||||
threadData.gc().ScheduleAndWaitFullGC();
|
||||
|
||||
EXPECT_THAT(Alive(threadData), testing::UnorderedElementsAre(global1.header()));
|
||||
done = true;
|
||||
|
||||
@@ -257,6 +257,7 @@ extern "C" const char* Kotlin_callsCheckerGoodFunctionNames[] = {
|
||||
"llvm.va_start",
|
||||
"llvm.x86.avx2.*",
|
||||
"llvm.x86.ssse3.*",
|
||||
"llvm.x86.sse2.*",
|
||||
"llvm.uadd.sat.*",
|
||||
"llvm.aarch64.neon.*",
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ extern "C" void DeinitMemory(MemoryState* state, bool destroyRuntime) {
|
||||
auto* node = mm::FromMemoryState(state);
|
||||
if (destroyRuntime) {
|
||||
ThreadStateGuard guard(state, ThreadState::kRunnable);
|
||||
node->Get()->gc().PerformFullGC();
|
||||
node->Get()->gc().ScheduleAndWaitFullGC();
|
||||
// TODO: Also make sure that finalizers are run.
|
||||
}
|
||||
mm::ThreadRegistry::Instance().Unregister(node);
|
||||
@@ -293,7 +293,7 @@ extern "C" RUNTIME_NOTHROW void GC_CollectorCallback(void* worker) {
|
||||
|
||||
extern "C" void Kotlin_native_internal_GC_collect(ObjHeader*) {
|
||||
auto* threadData = mm::ThreadRegistry::Instance().CurrentThreadData();
|
||||
threadData->gc().PerformFullGC();
|
||||
threadData->gc().ScheduleAndWaitFullGC();
|
||||
}
|
||||
|
||||
extern "C" void Kotlin_native_internal_GC_collectCyclic(ObjHeader*) {
|
||||
@@ -402,7 +402,7 @@ extern "C" void Kotlin_Any_share(ObjHeader* thiz) {
|
||||
}
|
||||
|
||||
extern "C" RUNTIME_NOTHROW void PerformFullGC(MemoryState* memory) {
|
||||
memory->GetThreadData()->gc().PerformFullGC();
|
||||
memory->GetThreadData()->gc().ScheduleAndWaitFullGC();
|
||||
}
|
||||
|
||||
extern "C" bool TryAddHeapRef(const ObjHeader* object) {
|
||||
|
||||
@@ -266,8 +266,20 @@ public:
|
||||
|
||||
Consumer() noexcept = default;
|
||||
|
||||
Consumer(Consumer&&) noexcept = default;
|
||||
Consumer& operator=(Consumer&&) noexcept = default;
|
||||
Consumer(Consumer&& other) noexcept {
|
||||
root_ = std::move(other.root_);
|
||||
size_ = other.size_;
|
||||
last_ = other.last_;
|
||||
other.size_ = 0;
|
||||
other.last_ = nullptr;
|
||||
}
|
||||
Consumer& operator=(Consumer&& other) noexcept {
|
||||
Consumer temp = std::move(other);
|
||||
std::swap(root_, temp.root_);
|
||||
std::swap(size_, temp.size_);
|
||||
std::swap(last_, temp.last_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
~Consumer() {
|
||||
// Make sure not to blow up the stack by nested `~Node` calls.
|
||||
@@ -280,6 +292,22 @@ public:
|
||||
Iterator begin() noexcept { return Iterator(root_.get()); }
|
||||
Iterator end() noexcept { return Iterator(nullptr); }
|
||||
|
||||
void MergeWith(Consumer &&other) {
|
||||
AssertCorrect();
|
||||
if (other.root_) {
|
||||
if (!root_) {
|
||||
root_ = std::move(other.root_);
|
||||
} else {
|
||||
last_->next_ = std::move(other.root_);
|
||||
}
|
||||
last_ = other.last_;
|
||||
size_ += other.size_;
|
||||
other.last_ = nullptr;
|
||||
other.size_ = 0;
|
||||
}
|
||||
AssertCorrect();
|
||||
}
|
||||
|
||||
private:
|
||||
friend class ObjectFactoryStorage;
|
||||
|
||||
@@ -624,6 +652,10 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void MergeWith(FinalizerQueue &&other) {
|
||||
consumer_.MergeWith(std::move(other.consumer_));
|
||||
}
|
||||
|
||||
Iterable IterForTests() noexcept { return Iterable(*this); }
|
||||
|
||||
private:
|
||||
|
||||
@@ -478,6 +478,91 @@ TEST(ObjectFactoryStorageTest, MoveAll) {
|
||||
EXPECT_THAT(consumer.size(), 3);
|
||||
}
|
||||
|
||||
TEST(ObjectFactoryStorageTest, MergeWith) {
|
||||
ObjectFactoryStorageRegular storage;
|
||||
Producer<ObjectFactoryStorageRegular> producer(storage, SimpleAllocator());
|
||||
Consumer<ObjectFactoryStorageRegular> consumer1;
|
||||
Consumer<ObjectFactoryStorageRegular> consumer2;
|
||||
|
||||
|
||||
producer.Insert<int>(1);
|
||||
producer.Insert<int>(2);
|
||||
producer.Insert<int>(3);
|
||||
producer.Insert<int>(4);
|
||||
producer.Insert<int>(5);
|
||||
|
||||
producer.Publish();
|
||||
|
||||
{
|
||||
auto iter = storage.LockForIter();
|
||||
for (auto it = iter.begin(); it != iter.end();) {
|
||||
if (it->Data<int>() % 2 == 0) {
|
||||
iter.MoveAndAdvance(consumer1, it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
auto iter = storage.LockForIter();
|
||||
for (auto it = iter.begin(); it != iter.end();) {
|
||||
iter.MoveAndAdvance(consumer2, it);
|
||||
}
|
||||
}
|
||||
|
||||
auto actual = Collect<int>(storage);
|
||||
EXPECT_THAT(actual, testing::IsEmpty());
|
||||
EXPECT_THAT(storage.GetSizeUnsafe(), 0);
|
||||
EXPECT_THAT(producer.size(), 0);
|
||||
|
||||
{
|
||||
auto actualConsumer1 = Collect<int, alignof(void*)>(consumer1);
|
||||
auto actualConsumer2 = Collect<int, alignof(void*)>(consumer2);
|
||||
|
||||
EXPECT_THAT(actualConsumer1, testing::ElementsAre(2, 4));
|
||||
EXPECT_THAT(consumer1.size(), 2);
|
||||
EXPECT_THAT(actualConsumer2, testing::ElementsAre(1, 3, 5));
|
||||
EXPECT_THAT(consumer2.size(), 3);
|
||||
}
|
||||
|
||||
consumer1.MergeWith(std::move(consumer2));
|
||||
{
|
||||
auto actualConsumer1 = Collect<int, alignof(void*)>(consumer1);
|
||||
auto actualConsumer2 = Collect<int, alignof(void*)>(consumer2);
|
||||
EXPECT_THAT(actualConsumer1, testing::ElementsAre(2, 4, 1, 3, 5));
|
||||
EXPECT_THAT(consumer1.size(), 5);
|
||||
EXPECT_THAT(actualConsumer2, testing::ElementsAre());
|
||||
EXPECT_THAT(consumer2.size(), 0);
|
||||
}
|
||||
|
||||
Consumer<ObjectFactoryStorageRegular> consumer3;
|
||||
consumer1.MergeWith(std::move(consumer3));
|
||||
{
|
||||
auto actualConsumer1 = Collect<int, alignof(void*)>(consumer1);
|
||||
auto actualConsumer2 = Collect<int, alignof(void*)>(consumer2);
|
||||
auto actualConsumer3 = Collect<int, alignof(void*)>(consumer3);
|
||||
EXPECT_THAT(actualConsumer1, testing::ElementsAre(2, 4, 1, 3, 5));
|
||||
EXPECT_THAT(consumer1.size(), 5);
|
||||
EXPECT_THAT(actualConsumer2, testing::ElementsAre());
|
||||
EXPECT_THAT(consumer2.size(), 0);
|
||||
EXPECT_THAT(actualConsumer3, testing::ElementsAre());
|
||||
EXPECT_THAT(consumer3.size(), 0);
|
||||
}
|
||||
|
||||
consumer3.MergeWith(std::move(consumer1));
|
||||
{
|
||||
auto actualConsumer1 = Collect<int, alignof(void*)>(consumer1);
|
||||
auto actualConsumer2 = Collect<int, alignof(void*)>(consumer2);
|
||||
auto actualConsumer3 = Collect<int, alignof(void*)>(consumer3);
|
||||
EXPECT_THAT(actualConsumer1, testing::ElementsAre());
|
||||
EXPECT_THAT(consumer1.size(), 0);
|
||||
EXPECT_THAT(actualConsumer2, testing::ElementsAre());
|
||||
EXPECT_THAT(consumer2.size(), 0);
|
||||
EXPECT_THAT(actualConsumer3, testing::ElementsAre(2, 4, 1, 3, 5));
|
||||
EXPECT_THAT(consumer3.size(), 5);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ObjectFactoryStorageTest, MoveTheOnlyElement) {
|
||||
ObjectFactoryStorageRegular storage;
|
||||
Producer<ObjectFactoryStorageRegular> producer(storage, SimpleAllocator());
|
||||
|
||||
@@ -54,10 +54,13 @@ NO_EXTERNAL_CALLS_CHECK void kotlin::mm::ThreadSuspensionData::suspendIfRequeste
|
||||
std::unique_lock lock(gSuspensionMutex);
|
||||
if (IsThreadSuspensionRequested()) {
|
||||
auto threadId = konan::currentThreadId();
|
||||
auto suspendStartMs = konan::getTimeMicros();
|
||||
RuntimeLogDebug({kTagGC, kTagMM}, "Suspending thread %d", threadId);
|
||||
AutoReset scopedAssign(&suspended_, true);
|
||||
gSuspendsionCondVar.wait(lock, []() { return !IsThreadSuspensionRequested(); });
|
||||
RuntimeLogDebug({kTagGC, kTagMM}, "Resuming thread %d", threadId);
|
||||
auto suspendEndMs = konan::getTimeMicros();
|
||||
RuntimeLogDebug({kTagGC, kTagMM}, "Resuming thread %d after %" PRIu64 " microseconds of suspension",
|
||||
threadId, suspendEndMs - suspendStartMs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user