blob: 212ca7c5a6dd214dfb596c2ab240ca86bc7af515 [file]
/*
* Copyright 2010-2023 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 "ExternalRCRefRegistry.hpp"
#include <condition_variable>
#include <mutex>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "ExternalRCRef.hpp"
#include "ObjectTestSupport.hpp"
#include "TestSupport.hpp"
#include "ThreadRegistry.hpp"
using namespace kotlin;
namespace {
class Waiter : private Pinned {
public:
void allow() noexcept {
{
std::unique_lock guard(mutex_);
allow_ = true;
}
cv_.notify_all();
};
void wait() noexcept {
std::unique_lock guard(mutex_);
cv_.wait(guard, [this] { return allow_; });
}
private:
bool allow_ = false;
std::mutex mutex_;
std::condition_variable cv_;
};
struct Payload {
static constexpr test_support::NoRefFields<Payload> kFields = {};
};
test_support::TypeInfoHolder typeInfoHolder{test_support::TypeInfoHolder::ObjectBuilder<Payload>()};
struct HeapObject : test_support::Object<Payload> {
HeapObject() noexcept : Object(typeInfoHolder.typeInfo()) {}
};
struct PermanentObject : test_support::Object<Payload> {
PermanentObject() noexcept : Object(typeInfoHolder.typeInfo()) {
header()->typeInfoOrMeta_ = setPointerBits(header()->typeInfoOrMeta_, OBJECT_TAG_PERMANENT);
RuntimeAssert(header()->permanent(), "Must be permanent");
}
};
} // namespace
class ExternalRCRefRegistryTest : public testing::Test {
public:
~ExternalRCRefRegistryTest() {
// Clean up safely.
roots();
all();
}
void publish() noexcept { mm::ThreadRegistry::Instance().CurrentThreadData()->externalRCRefRegistry().publish(); }
template <typename... Invalidated>
std::vector<ObjHeader*> all(Invalidated&&... invalidated) noexcept {
std::set<ObjHeader*> invalidatedSet({std::forward<Invalidated>(invalidated)...});
std::vector<ObjHeader*> result;
for (auto obj : mm::ExternalRCRefRegistry::instance().lockForIter()) {
if (invalidatedSet.find(obj) != invalidatedSet.end()) {
obj = nullptr;
}
result.push_back(obj);
}
return result;
}
std::vector<ObjHeader*> roots() noexcept {
std::vector<ObjHeader*> result;
for (auto* obj : mm::ExternalRCRefRegistry::instance().roots()) {
result.push_back(obj);
}
return result;
}
template <typename Traits>
ObjHeader* tryRef(mm::ExternalRCRef<Traits>& weakRef) noexcept {
ObjHeader* result;
return weakRef.tryRef(&result);
}
};
TEST_F(ExternalRCRefRegistryTest, RegisterRefsWithoutPublish) {
RunInNewThread([this] {
HeapObject object1;
HeapObject object2;
PermanentObject object3;
PermanentObject object4;
auto ref1 = mm::OwningExternalRCRef(object1.header());
auto ref2 = mm::WeakExternalRCRef(object2.header());
auto ref3 = mm::OwningExternalRCRef(object3.header());
auto ref4 = mm::WeakExternalRCRef(object4.header());
EXPECT_THAT(roots(), testing::UnorderedElementsAre(object1.header()));
EXPECT_THAT(all(), testing::UnorderedElementsAre());
EXPECT_THAT(*ref1, object1.header());
EXPECT_THAT(tryRef(ref1), object1.header());
EXPECT_THAT(tryRef(ref2), object2.header());
EXPECT_THAT(*ref3, object3.header());
EXPECT_THAT(tryRef(ref3), object3.header());
EXPECT_THAT(tryRef(ref4), object4.header());
ref1.reset();
ref2.reset();
ref3.reset();
ref4.reset();
EXPECT_THAT(*ref1, nullptr);
EXPECT_THAT(tryRef(ref1), nullptr);
EXPECT_THAT(tryRef(ref2), nullptr);
EXPECT_THAT(*ref3, nullptr);
EXPECT_THAT(tryRef(ref3), nullptr);
EXPECT_THAT(tryRef(ref4), nullptr);
EXPECT_THAT(roots(), testing::UnorderedElementsAre());
EXPECT_THAT(all(), testing::UnorderedElementsAre());
publish();
EXPECT_THAT(roots(), testing::UnorderedElementsAre());
EXPECT_THAT(all(), testing::UnorderedElementsAre());
});
}
TEST_F(ExternalRCRefRegistryTest, RegisterAllRefs) {
RunInNewThread([this] {
HeapObject object1;
HeapObject object2;
PermanentObject object3;
PermanentObject object4;
auto ref1 = mm::OwningExternalRCRef(object1.header());
auto ref2 = mm::WeakExternalRCRef(object2.header());
auto ref3 = mm::OwningExternalRCRef(object3.header());
auto ref4 = mm::WeakExternalRCRef(object4.header());
EXPECT_THAT(roots(), testing::UnorderedElementsAre(object1.header()));
EXPECT_THAT(all(), testing::UnorderedElementsAre());
EXPECT_THAT(*ref1, object1.header());
EXPECT_THAT(tryRef(ref1), object1.header());
EXPECT_THAT(tryRef(ref2), object2.header());
EXPECT_THAT(*ref3, object3.header());
EXPECT_THAT(tryRef(ref3), object3.header());
EXPECT_THAT(tryRef(ref4), object4.header());
publish();
EXPECT_THAT(roots(), testing::UnorderedElementsAre(object1.header()));
EXPECT_THAT(all(), testing::UnorderedElementsAre(object1.header(), object2.header()));
EXPECT_THAT(*ref1, object1.header());
EXPECT_THAT(tryRef(ref1), object1.header());
EXPECT_THAT(tryRef(ref2), object2.header());
EXPECT_THAT(*ref3, object3.header());
EXPECT_THAT(tryRef(ref3), object3.header());
EXPECT_THAT(tryRef(ref4), object4.header());
ref1.reset();
ref2.reset();
ref3.reset();
ref4.reset();
EXPECT_THAT(*ref1, nullptr);
EXPECT_THAT(tryRef(ref1), nullptr);
EXPECT_THAT(tryRef(ref2), nullptr);
EXPECT_THAT(*ref3, nullptr);
EXPECT_THAT(tryRef(ref3), nullptr);
EXPECT_THAT(tryRef(ref4), nullptr);
EXPECT_THAT(roots(), testing::UnorderedElementsAre());
EXPECT_THAT(all(), testing::UnorderedElementsAre());
});
}
TEST_F(ExternalRCRefRegistryTest, InvalidateWeakRef) {
RunInNewThread([this] {
HeapObject object;
auto ref = mm::WeakExternalRCRef(object.header());
publish();
EXPECT_THAT(roots(), testing::UnorderedElementsAre());
EXPECT_THAT(all(object.header()), testing::UnorderedElementsAre(nullptr));
EXPECT_THAT(tryRef(ref), nullptr);
EXPECT_THAT(roots(), testing::UnorderedElementsAre());
EXPECT_THAT(all(), testing::UnorderedElementsAre(nullptr));
EXPECT_THAT(tryRef(ref), nullptr);
ref.reset();
EXPECT_THAT(roots(), testing::UnorderedElementsAre());
EXPECT_THAT(all(), testing::UnorderedElementsAre());
});
}
TEST_F(ExternalRCRefRegistryTest, RetainWeakRef) {
RunInNewThread([this] {
HeapObject object;
auto ref = mm::WeakExternalRCRef(object.header());
publish();
EXPECT_THAT(roots(), testing::UnorderedElementsAre());
EXPECT_THAT(all(), testing::UnorderedElementsAre(object.header()));
EXPECT_THAT(tryRef(ref), object.header());
mm::retainExternalRCRef(ref.get());
EXPECT_THAT(roots(), testing::UnorderedElementsAre(object.header()));
EXPECT_THAT(all(), testing::UnorderedElementsAre(object.header()));
mm::releaseExternalRCRef(ref.get());
EXPECT_THAT(roots(), testing::UnorderedElementsAre());
EXPECT_THAT(all(), testing::UnorderedElementsAre(object.header()));
EXPECT_THAT(tryRef(ref), object.header());
ref.reset();
EXPECT_THAT(roots(), testing::UnorderedElementsAre());
EXPECT_THAT(all(), testing::UnorderedElementsAre());
});
}
TEST_F(ExternalRCRefRegistryTest, RetainWeakRefBeforePublish) {
RunInNewThread([this] {
HeapObject object;
auto ref = mm::WeakExternalRCRef(object.header());
mm::retainExternalRCRef(ref.get());
EXPECT_THAT(roots(), testing::UnorderedElementsAre(object.header()));
EXPECT_THAT(all(), testing::UnorderedElementsAre());
EXPECT_THAT(tryRef(ref), object.header());
publish();
EXPECT_THAT(roots(), testing::UnorderedElementsAre(object.header()));
EXPECT_THAT(all(), testing::UnorderedElementsAre(object.header()));
EXPECT_THAT(tryRef(ref), object.header());
mm::releaseExternalRCRef(ref.get());
EXPECT_THAT(roots(), testing::UnorderedElementsAre());
EXPECT_THAT(all(), testing::UnorderedElementsAre(object.header()));
EXPECT_THAT(tryRef(ref), object.header());
ref.reset();
EXPECT_THAT(roots(), testing::UnorderedElementsAre());
EXPECT_THAT(all(), testing::UnorderedElementsAre());
});
}
TEST_F(ExternalRCRefRegistryTest, StressOwningRef) {
HeapObject object;
Waiter waiter;
std::vector<ScopedThread> mutators;
for (int i = 0; i < kDefaultThreadCount; ++i) {
mutators.emplace_back([&, this] {
ScopedMemoryInit scope;
waiter.wait();
auto ref = mm::OwningExternalRCRef(object.header());
publish();
ref.reset();
});
}
waiter.allow();
mutators.clear();
EXPECT_THAT(roots(), testing::UnorderedElementsAre());
EXPECT_THAT(all(), testing::UnorderedElementsAre());
}
TEST_F(ExternalRCRefRegistryTest, StressWeakRef) {
HeapObject object;
Waiter waiter;
std::vector<ScopedThread> mutators;
for (int i = 0; i < kDefaultThreadCount; ++i) {
mutators.emplace_back([&, this] {
ScopedMemoryInit scope;
waiter.wait();
auto ref = mm::WeakExternalRCRef(object.header());
publish();
ref.reset();
});
}
waiter.allow();
mutators.clear();
EXPECT_THAT(roots(), testing::UnorderedElementsAre());
EXPECT_THAT(all(), testing::UnorderedElementsAre());
}
TEST_F(ExternalRCRefRegistryTest, StressWeakRefRetainRelease) {
RunInNewThread([this] {
constexpr int kGCCycles = 10000;
constexpr int kRefsCount = 3;
HeapObject object;
KRef obj = object.header();
Waiter waiter;
std::atomic<bool> canStop = false;
std::vector<mm::WeakExternalRCRef> refs;
for (int i = 0; i < kRefsCount; ++i) {
refs.emplace_back(mm::WeakExternalRCRef(obj));
}
publish();
std::vector<ScopedThread> mutators;
mutators.emplace_back([&, this] {
waiter.wait();
for (int i = 0; i < kGCCycles; ++i) {
roots();
all();
}
canStop.store(true, std::memory_order_release);
});
for (int i = 0; i < kDefaultThreadCount; ++i) {
mutators.emplace_back([i, &refs, &waiter, &canStop] {
ScopedMemoryInit scope;
waiter.wait();
auto& ref = refs[i % kRefsCount];
while (!canStop.load(std::memory_order_acquire)) {
mm::retainExternalRCRef(ref.get());
mm::releaseExternalRCRef(ref.get());
}
});
}
waiter.allow();
mutators.clear();
for (auto& ref : refs) {
ref.reset();
}
EXPECT_THAT(roots(), testing::UnorderedElementsAre());
EXPECT_THAT(all(), testing::UnorderedElementsAre());
});
}