[K/N][Runtime] Support thread state switching in spin locks
Issue #KT-49013 Fixed
This commit is contained in:
@@ -227,14 +227,14 @@ class CycleDetector : private kotlin::Pinned, public KonanAllocatorAware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void insertCandidate(KRef candidate) {
|
void insertCandidate(KRef candidate) {
|
||||||
std::lock_guard<kotlin::SpinLock> guard(lock_);
|
std::lock_guard guard(lock_);
|
||||||
|
|
||||||
auto it = candidateList_.insert(candidateList_.begin(), candidate);
|
auto it = candidateList_.insert(candidateList_.begin(), candidate);
|
||||||
candidateInList_.emplace(candidate, it);
|
candidateInList_.emplace(candidate, it);
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeCandidate(KRef candidate) {
|
void removeCandidate(KRef candidate) {
|
||||||
std::lock_guard<kotlin::SpinLock> guard(lock_);
|
std::lock_guard guard(lock_);
|
||||||
|
|
||||||
auto it = candidateInList_.find(candidate);
|
auto it = candidateInList_.find(candidate);
|
||||||
if (it == candidateInList_.end())
|
if (it == candidateInList_.end())
|
||||||
@@ -243,7 +243,7 @@ class CycleDetector : private kotlin::Pinned, public KonanAllocatorAware {
|
|||||||
candidateInList_.erase(it);
|
candidateInList_.erase(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin::SpinLock lock_;
|
kotlin::SpinLock<kotlin::MutexThreadStateHandling::kIgnore> lock_;
|
||||||
using CandidateList = KStdList<KRef>;
|
using CandidateList = KStdList<KRef>;
|
||||||
CandidateList candidateList_;
|
CandidateList candidateList_;
|
||||||
KStdUnorderedMap<KRef, CandidateList::iterator> candidateInList_;
|
KStdUnorderedMap<KRef, CandidateList::iterator> candidateInList_;
|
||||||
@@ -2994,7 +2994,7 @@ ScopedRefHolder::~ScopedRefHolder() {
|
|||||||
CycleDetectorRootset CycleDetector::collectRootset() {
|
CycleDetectorRootset CycleDetector::collectRootset() {
|
||||||
auto& detector = instance();
|
auto& detector = instance();
|
||||||
CycleDetectorRootset rootset;
|
CycleDetectorRootset rootset;
|
||||||
std::lock_guard<kotlin::SpinLock> guard(detector.lock_);
|
std::lock_guard guard(detector.lock_);
|
||||||
for (auto* candidate: detector.candidateList_) {
|
for (auto* candidate: detector.candidateList_) {
|
||||||
// Only frozen candidates are to be analyzed.
|
// Only frozen candidates are to be analyzed.
|
||||||
if (!isPermanentOrFrozen(candidate))
|
if (!isPermanentOrFrozen(candidate))
|
||||||
|
|||||||
@@ -47,18 +47,6 @@ void ThrowException(KRef exception) {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// This function accesses a TLS variable under the hood, so it must not be called from a thread destructor
|
|
||||||
// (see kotlin::mm::IsCurrentThreadRegistered, kotlin::mm::GetMemoryState()).
|
|
||||||
[[nodiscard]] kotlin::ThreadStateGuard setNativeStateForRegisteredThread(bool reentrant = true) {
|
|
||||||
if (kotlin::mm::IsCurrentThreadRegistered()) {
|
|
||||||
return kotlin::ThreadStateGuard(kotlin::ThreadState::kNative, reentrant);
|
|
||||||
} else {
|
|
||||||
// The current thread is not registered in the Kotlin runtime,
|
|
||||||
// just return an empty guard which doesn't actually switch the state.
|
|
||||||
return kotlin::ThreadStateGuard();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class {
|
class {
|
||||||
/**
|
/**
|
||||||
* Timeout 5 sec for concurrent (second) terminate attempt to give a chance the first one to finish.
|
* Timeout 5 sec for concurrent (second) terminate attempt to give a chance the first one to finish.
|
||||||
@@ -73,7 +61,7 @@ class {
|
|||||||
// block() is supposed to be NORETURN, otherwise go to normal abort()
|
// block() is supposed to be NORETURN, otherwise go to normal abort()
|
||||||
konan::abort();
|
konan::abort();
|
||||||
} else {
|
} else {
|
||||||
auto guard = setNativeStateForRegisteredThread();
|
kotlin::NativeOrUnregisteredThreadGuard guard(/* reentrant = */ true);
|
||||||
sleep(timeoutSec);
|
sleep(timeoutSec);
|
||||||
// We come here when another terminate handler hangs for 5 sec, that looks fatally broken. Go to forced exit now.
|
// We come here when another terminate handler hangs for 5 sec, that looks fatally broken. Go to forced exit now.
|
||||||
}
|
}
|
||||||
@@ -147,12 +135,12 @@ class TerminateHandler : private kotlin::Pinned {
|
|||||||
terminateWithUnhandledException(e.GetExceptionObject());
|
terminateWithUnhandledException(e.GetExceptionObject());
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
// Not a Kotlin exception - call default handler
|
// Not a Kotlin exception - call default handler
|
||||||
auto guard = setNativeStateForRegisteredThread();
|
kotlin::NativeOrUnregisteredThreadGuard guard(/* reentrant = */ true);
|
||||||
queuedHandler();
|
queuedHandler();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Come here in case of direct terminate() call or unknown exception - go to default terminate handler.
|
// Come here in case of direct terminate() call or unknown exception - go to default terminate handler.
|
||||||
auto guard = setNativeStateForRegisteredThread();
|
kotlin::NativeOrUnregisteredThreadGuard guard(/* reentrant = */ true);
|
||||||
queuedHandler();
|
queuedHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -497,6 +497,20 @@ ALWAYS_INLINE inline R CallWithThreadState(R(*function)(Args...), Args... args)
|
|||||||
return function(std::forward<Args>(args)...);
|
return function(std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class NativeOrUnregisteredThreadGuard final : private MoveOnly {
|
||||||
|
public:
|
||||||
|
explicit NativeOrUnregisteredThreadGuard(bool reentrant = false) noexcept {
|
||||||
|
// The default ctor of ThreadStateGuard doesn't set the state.
|
||||||
|
// So the actual state switching is performed only if the thread is registered.
|
||||||
|
if (kotlin::mm::IsCurrentThreadRegistered()) {
|
||||||
|
backingGuard_ = kotlin::ThreadStateGuard(kotlin::ThreadState::kNative, reentrant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ThreadStateGuard backingGuard_;
|
||||||
|
};
|
||||||
|
|
||||||
extern const bool kSupportsMultipleMutators;
|
extern const bool kSupportsMultipleMutators;
|
||||||
|
|
||||||
} // namespace kotlin
|
} // namespace kotlin
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
namespace kotlin {
|
namespace kotlin {
|
||||||
|
|
||||||
// A queue that is constructed by collecting subqueues from several `Producer`s.
|
// A queue that is constructed by collecting subqueues from several `Producer`s.
|
||||||
template <typename T>
|
template <typename T, typename Mutex>
|
||||||
class MultiSourceQueue {
|
class MultiSourceQueue {
|
||||||
public:
|
public:
|
||||||
class Producer;
|
class Producer;
|
||||||
@@ -66,7 +66,7 @@ public:
|
|||||||
for (auto& node : queue_) {
|
for (auto& node : queue_) {
|
||||||
node.owner_ = nullptr;
|
node.owner_ = nullptr;
|
||||||
}
|
}
|
||||||
std::lock_guard<SpinLock> guard(owner_.mutex_);
|
std::lock_guard<Mutex> guard(owner_.mutex_);
|
||||||
owner_.queue_.splice(owner_.queue_.end(), queue_);
|
owner_.queue_.splice(owner_.queue_.end(), queue_);
|
||||||
owner_.deletionQueue_.splice(owner_.deletionQueue_.end(), deletionQueue_);
|
owner_.deletionQueue_.splice(owner_.deletionQueue_.end(), deletionQueue_);
|
||||||
}
|
}
|
||||||
@@ -114,7 +114,7 @@ public:
|
|||||||
explicit Iterable(MultiSourceQueue& owner) noexcept : owner_(owner), guard_(owner_.mutex_) {}
|
explicit Iterable(MultiSourceQueue& owner) noexcept : owner_(owner), guard_(owner_.mutex_) {}
|
||||||
|
|
||||||
MultiSourceQueue& owner_; // weak
|
MultiSourceQueue& owner_; // weak
|
||||||
std::unique_lock<SpinLock> guard_;
|
std::unique_lock<Mutex> guard_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Lock `MultiSourceQueue` for safe iteration. If element was scheduled for deletion,
|
// Lock `MultiSourceQueue` for safe iteration. If element was scheduled for deletion,
|
||||||
@@ -123,7 +123,7 @@ public:
|
|||||||
|
|
||||||
// Lock `MultiSourceQueue` and apply deletions. Only deletes elements that were published.
|
// Lock `MultiSourceQueue` and apply deletions. Only deletes elements that were published.
|
||||||
void ApplyDeletions() noexcept {
|
void ApplyDeletions() noexcept {
|
||||||
std::lock_guard<SpinLock> guard(mutex_);
|
std::lock_guard<Mutex> guard(mutex_);
|
||||||
KStdList<Node*> remainingDeletions;
|
KStdList<Node*> remainingDeletions;
|
||||||
|
|
||||||
auto it = deletionQueue_.begin();
|
auto it = deletionQueue_.begin();
|
||||||
@@ -154,7 +154,7 @@ private:
|
|||||||
// which is important for GC mark phase.
|
// which is important for GC mark phase.
|
||||||
KStdList<Node> queue_;
|
KStdList<Node> queue_;
|
||||||
KStdList<Node*> deletionQueue_;
|
KStdList<Node*> deletionQueue_;
|
||||||
SpinLock mutex_;
|
Mutex mutex_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace kotlin
|
} // namespace kotlin
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ using namespace kotlin;
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
template <typename T>
|
template <typename T, typename Mutex>
|
||||||
KStdVector<T> Collect(MultiSourceQueue<T>& queue) {
|
KStdVector<T> Collect(MultiSourceQueue<T, Mutex>& queue) {
|
||||||
KStdVector<T> result;
|
KStdVector<T> result;
|
||||||
for (const auto& element : queue.LockForIter()) {
|
for (const auto& element : queue.LockForIter()) {
|
||||||
result.push_back(element);
|
result.push_back(element);
|
||||||
@@ -29,7 +29,7 @@ KStdVector<T> Collect(MultiSourceQueue<T>& queue) {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
using IntQueue = MultiSourceQueue<int>;
|
using IntQueue = MultiSourceQueue<int, SpinLock<MutexThreadStateHandling::kIgnore>>;
|
||||||
|
|
||||||
TEST(MultiSourceQueueTest, Insert) {
|
TEST(MultiSourceQueueTest, Insert) {
|
||||||
IntQueue queue;
|
IntQueue queue;
|
||||||
|
|||||||
@@ -20,11 +20,20 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
#include "KAssert.h"
|
#include "KAssert.h"
|
||||||
|
#include "Memory.h"
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
|
|
||||||
namespace kotlin {
|
namespace kotlin {
|
||||||
|
|
||||||
class SpinLock : private Pinned {
|
enum class MutexThreadStateHandling {
|
||||||
|
kIgnore, kSwitchIfRegistered
|
||||||
|
};
|
||||||
|
|
||||||
|
template <MutexThreadStateHandling threadStateHandling>
|
||||||
|
class SpinLock;
|
||||||
|
|
||||||
|
template <>
|
||||||
|
class SpinLock<MutexThreadStateHandling::kIgnore> : private Pinned {
|
||||||
public:
|
public:
|
||||||
void lock() noexcept {
|
void lock() noexcept {
|
||||||
while (!__sync_bool_compare_and_swap(&atomicInt, 0, 1)) {
|
while (!__sync_bool_compare_and_swap(&atomicInt, 0, 1)) {
|
||||||
@@ -43,6 +52,32 @@ private:
|
|||||||
int32_t atomicInt = 0;
|
int32_t atomicInt = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
class SpinLock<MutexThreadStateHandling::kSwitchIfRegistered> : private Pinned {
|
||||||
|
public:
|
||||||
|
void lock() noexcept {
|
||||||
|
// Fast path without thread state switching.
|
||||||
|
if (__sync_bool_compare_and_swap(&atomicInt, 0, 1)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin::NativeOrUnregisteredThreadGuard guard(/* reentrant = */ true);
|
||||||
|
while (!__sync_bool_compare_and_swap(&atomicInt, 0, 1)) {
|
||||||
|
// TODO: yield.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void unlock() noexcept {
|
||||||
|
if (!__sync_bool_compare_and_swap(&atomicInt, 1, 0)) {
|
||||||
|
RuntimeAssert(false, "Unable to unlock");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// TODO: Consider using `std::atomic_flag`.
|
||||||
|
int32_t atomicInt = 0;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace kotlin
|
} // namespace kotlin
|
||||||
|
|
||||||
#endif // RUNTIME_MUTEX_H
|
#endif // RUNTIME_MUTEX_H
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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 "Mutex.hpp"
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include "TestSupport.hpp"
|
||||||
|
|
||||||
|
using namespace kotlin;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class MutexTest : public testing::Test {};
|
||||||
|
|
||||||
|
using LockTypes = testing::Types<SpinLock<MutexThreadStateHandling::kIgnore>, SpinLock<MutexThreadStateHandling::kSwitchIfRegistered>>;
|
||||||
|
TYPED_TEST_SUITE(MutexTest, LockTypes);
|
||||||
|
|
||||||
|
TYPED_TEST(MutexTest, SmokeDetachedThread) {
|
||||||
|
using LockUnderTest = TypeParam;
|
||||||
|
|
||||||
|
LockUnderTest lock;
|
||||||
|
std::thread secondThread;
|
||||||
|
std::atomic<bool> started = false;
|
||||||
|
std::atomic<int32_t> protectedCounter = 0;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock guard1(lock);
|
||||||
|
secondThread = std::thread([&lock, &started, &protectedCounter]() {
|
||||||
|
started = true;
|
||||||
|
std::unique_lock guard2(lock);
|
||||||
|
protectedCounter++;
|
||||||
|
});
|
||||||
|
|
||||||
|
protectedCounter++;
|
||||||
|
while (!started) { std::this_thread::yield(); }
|
||||||
|
|
||||||
|
// Wait to give the second thread a chance to try to acquire the lock.
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
EXPECT_EQ(protectedCounter, 1);
|
||||||
|
}
|
||||||
|
secondThread.join();
|
||||||
|
EXPECT_EQ(protectedCounter, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -982,7 +982,7 @@ static const TypeInfo* createTypeInfo(Class clazz, const TypeInfo* superType, co
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static kotlin::SpinLock typeInfoCreationMutex;
|
static kotlin::SpinLock<kotlin::MutexThreadStateHandling::kSwitchIfRegistered> typeInfoCreationMutex;
|
||||||
|
|
||||||
static const TypeInfo* getOrCreateTypeInfo(Class clazz) {
|
static const TypeInfo* getOrCreateTypeInfo(Class clazz) {
|
||||||
const TypeInfo* result = Kotlin_ObjCExport_getAssociatedTypeInfo(clazz);
|
const TypeInfo* result = Kotlin_ObjCExport_getAssociatedTypeInfo(clazz);
|
||||||
@@ -996,7 +996,7 @@ static const TypeInfo* getOrCreateTypeInfo(Class clazz) {
|
|||||||
theForeignObjCObjectTypeInfo :
|
theForeignObjCObjectTypeInfo :
|
||||||
getOrCreateTypeInfo(superClass);
|
getOrCreateTypeInfo(superClass);
|
||||||
|
|
||||||
std::lock_guard<kotlin::SpinLock> lockGuard(typeInfoCreationMutex);
|
std::lock_guard lockGuard(typeInfoCreationMutex);
|
||||||
|
|
||||||
result = Kotlin_ObjCExport_getAssociatedTypeInfo(clazz); // double-checking.
|
result = Kotlin_ObjCExport_getAssociatedTypeInfo(clazz); // double-checking.
|
||||||
if (result == nullptr) {
|
if (result == nullptr) {
|
||||||
@@ -1016,7 +1016,7 @@ const TypeInfo* Kotlin_ObjCExport_createTypeInfoWithKotlinFieldsFrom(Class clazz
|
|||||||
return createTypeInfo(clazz, superType, fieldsInfo);
|
return createTypeInfo(clazz, superType, fieldsInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
static kotlin::SpinLock classCreationMutex;
|
static kotlin::SpinLock<kotlin::MutexThreadStateHandling::kSwitchIfRegistered> classCreationMutex;
|
||||||
static int anonymousClassNextId = 0;
|
static int anonymousClassNextId = 0;
|
||||||
|
|
||||||
static void addVirtualAdapters(Class clazz, const ObjCTypeAdapter* typeAdapter) {
|
static void addVirtualAdapters(Class clazz, const ObjCTypeAdapter* typeAdapter) {
|
||||||
@@ -1092,7 +1092,7 @@ static Class getOrCreateClass(const TypeInfo* typeInfo) {
|
|||||||
} else {
|
} else {
|
||||||
Class superClass = getOrCreateClass(typeInfo->superType_);
|
Class superClass = getOrCreateClass(typeInfo->superType_);
|
||||||
|
|
||||||
std::lock_guard<kotlin::SpinLock> lockGuard(classCreationMutex); // Note: non-recursive
|
std::lock_guard lockGuard(classCreationMutex); // Note: non-recursive
|
||||||
|
|
||||||
result = typeInfo->writableInfo_->objCExport.objCClass; // double-checking.
|
result = typeInfo->writableInfo_->objCExport.objCClass; // double-checking.
|
||||||
if (result == nullptr) {
|
if (result == nullptr) {
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ static void AddMethods(Class clazz, const struct ObjCMethodDescription* methods,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static kotlin::SpinLock classCreationMutex;
|
static kotlin::SpinLock<kotlin::MutexThreadStateHandling::kSwitchIfRegistered> classCreationMutex;
|
||||||
static int anonymousClassNextId = 0;
|
static int anonymousClassNextId = 0;
|
||||||
|
|
||||||
NO_EXTERNAL_CALLS_CHECK static Class allocateClass(const KotlinObjCClassInfo* info) {
|
NO_EXTERNAL_CALLS_CHECK static Class allocateClass(const KotlinObjCClassInfo* info) {
|
||||||
@@ -284,7 +284,7 @@ NO_EXTERNAL_CALLS_CHECK static Class allocateClass(const KotlinObjCClassInfo* in
|
|||||||
}
|
}
|
||||||
|
|
||||||
void* CreateKotlinObjCClass(const KotlinObjCClassInfo* info) {
|
void* CreateKotlinObjCClass(const KotlinObjCClassInfo* info) {
|
||||||
std::lock_guard<kotlin::SpinLock> lockGuard(classCreationMutex);
|
std::lock_guard lockGuard(classCreationMutex);
|
||||||
|
|
||||||
void* createdClass = *info->createdClass;
|
void* createdClass = *info->createdClass;
|
||||||
if (createdClass != nullptr) {
|
if (createdClass != nullptr) {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
namespace kotlin {
|
namespace kotlin {
|
||||||
|
|
||||||
// TODO: Consider different locking mechanisms.
|
// TODO: Consider different locking mechanisms.
|
||||||
template <typename Value, typename Mutex = SpinLock>
|
template <typename Value, typename Mutex>
|
||||||
class SingleLockList : private Pinned {
|
class SingleLockList : private Pinned {
|
||||||
public:
|
public:
|
||||||
class Node;
|
class Node;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ using namespace kotlin;
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
using IntList = SingleLockList<int>;
|
using IntList = SingleLockList<int, SpinLock<MutexThreadStateHandling::kIgnore>>;
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@@ -376,7 +376,7 @@ private:
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST(SingleLockListTest, PinnedType) {
|
TEST(SingleLockListTest, PinnedType) {
|
||||||
SingleLockList<PinnedType> list;
|
SingleLockList<PinnedType, SpinLock<MutexThreadStateHandling::kIgnore>> list;
|
||||||
constexpr int kFirst = 1;
|
constexpr int kFirst = 1;
|
||||||
|
|
||||||
auto* itemNode = list.Emplace(kFirst);
|
auto* itemNode = list.Emplace(kFirst);
|
||||||
@@ -414,7 +414,7 @@ private:
|
|||||||
TEST(SingleLockListTest, Destructor) {
|
TEST(SingleLockListTest, Destructor) {
|
||||||
testing::StrictMock<testing::MockFunction<DestructorHook>> hook;
|
testing::StrictMock<testing::MockFunction<DestructorHook>> hook;
|
||||||
{
|
{
|
||||||
SingleLockList<WithDestructorHook> list;
|
SingleLockList<WithDestructorHook, SpinLock<MutexThreadStateHandling::kIgnore>> list;
|
||||||
auto* first = list.Emplace(hook.AsStdFunction())->Get();
|
auto* first = list.Emplace(hook.AsStdFunction())->Get();
|
||||||
auto* second = list.Emplace(hook.AsStdFunction())->Get();
|
auto* second = list.Emplace(hook.AsStdFunction())->Get();
|
||||||
auto* third = list.Emplace(hook.AsStdFunction())->Get();
|
auto* third = list.Emplace(hook.AsStdFunction())->Get();
|
||||||
|
|||||||
@@ -15,16 +15,18 @@ namespace kotlin {
|
|||||||
namespace mm {
|
namespace mm {
|
||||||
|
|
||||||
class GlobalsRegistry : Pinned {
|
class GlobalsRegistry : Pinned {
|
||||||
|
using Mutex = SpinLock<MutexThreadStateHandling::kIgnore>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
class ThreadQueue : public MultiSourceQueue<ObjHeader**>::Producer {
|
class ThreadQueue : public MultiSourceQueue<ObjHeader**, Mutex>::Producer {
|
||||||
public:
|
public:
|
||||||
explicit ThreadQueue(GlobalsRegistry& registry) : Producer(registry.globals_) {}
|
explicit ThreadQueue(GlobalsRegistry& registry) : Producer(registry.globals_) {}
|
||||||
// Do not add fields as this is just a wrapper and Producer does not have virtual destructor.
|
// Do not add fields as this is just a wrapper and Producer does not have virtual destructor.
|
||||||
};
|
};
|
||||||
|
|
||||||
using Iterable = MultiSourceQueue<ObjHeader**>::Iterable;
|
using Iterable = MultiSourceQueue<ObjHeader**, Mutex>::Iterable;
|
||||||
|
|
||||||
using Iterator = MultiSourceQueue<ObjHeader**>::Iterator;
|
using Iterator = MultiSourceQueue<ObjHeader**, Mutex>::Iterator;
|
||||||
|
|
||||||
GlobalsRegistry();
|
GlobalsRegistry();
|
||||||
~GlobalsRegistry();
|
~GlobalsRegistry();
|
||||||
@@ -47,7 +49,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
// TODO: Add-only MultiSourceQueue can be made more efficient. Measure, if it's a problem.
|
// TODO: Add-only MultiSourceQueue can be made more efficient. Measure, if it's a problem.
|
||||||
MultiSourceQueue<ObjHeader**> globals_;
|
MultiSourceQueue<ObjHeader**, Mutex> globals_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace mm
|
} // namespace mm
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* 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 "Mutex.hpp"
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include "TestSupport.hpp"
|
||||||
|
#include "ThreadState.hpp"
|
||||||
|
|
||||||
|
using namespace kotlin;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
template <MutexThreadStateHandling Handling, ThreadState State>
|
||||||
|
class Mode {
|
||||||
|
public:
|
||||||
|
using Lock = SpinLock<Handling>;
|
||||||
|
static constexpr MutexThreadStateHandling threadStateHandling = Handling;
|
||||||
|
static constexpr ThreadState initialState = State;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class MutexTestNewMM : public testing::Test {};
|
||||||
|
|
||||||
|
using TestModes = testing::Types<
|
||||||
|
Mode<MutexThreadStateHandling::kIgnore, ThreadState::kRunnable>,
|
||||||
|
Mode<MutexThreadStateHandling::kIgnore, ThreadState::kNative>,
|
||||||
|
Mode<MutexThreadStateHandling::kSwitchIfRegistered, ThreadState::kRunnable>,
|
||||||
|
Mode<MutexThreadStateHandling::kSwitchIfRegistered, ThreadState::kNative>>;
|
||||||
|
|
||||||
|
TYPED_TEST_SUITE(MutexTestNewMM, TestModes);
|
||||||
|
|
||||||
|
TYPED_TEST(MutexTestNewMM, SmokeAttachedThread) {
|
||||||
|
RunInNewThread([](){
|
||||||
|
using LockUnderTest = typename TypeParam::Lock;
|
||||||
|
ThreadState initialThreadState = TypeParam::initialState;
|
||||||
|
|
||||||
|
LockUnderTest lock;
|
||||||
|
std::thread secondThread;
|
||||||
|
std::atomic<bool> started = false;
|
||||||
|
std::atomic<int32_t> protectedCounter = 0;
|
||||||
|
mm::ThreadData* secondThreadData = nullptr;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock guard1(lock);
|
||||||
|
secondThread = std::thread([&lock, &started, &protectedCounter, &secondThreadData, &initialThreadState]() {
|
||||||
|
ScopedMemoryInit init;
|
||||||
|
SwitchThreadState(init.memoryState(), initialThreadState, /* reentrant = */ true);
|
||||||
|
|
||||||
|
secondThreadData = init.memoryState()->GetThreadData();
|
||||||
|
ASSERT_EQ(secondThreadData->state(), initialThreadState);
|
||||||
|
|
||||||
|
started = true;
|
||||||
|
std::unique_lock guard2(lock);
|
||||||
|
EXPECT_EQ(secondThreadData->state(), initialThreadState);
|
||||||
|
protectedCounter++;
|
||||||
|
});
|
||||||
|
|
||||||
|
protectedCounter++;
|
||||||
|
while (!started) {
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait to give the second thread a chance to try to acquire the lock.
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
EXPECT_EQ(protectedCounter, 1);
|
||||||
|
|
||||||
|
auto expectedSecondThreadState =
|
||||||
|
(TypeParam::threadStateHandling == MutexThreadStateHandling::kSwitchIfRegistered)
|
||||||
|
? ThreadState::kNative
|
||||||
|
: initialThreadState;
|
||||||
|
EXPECT_EQ(secondThreadData->state(), expectedSecondThreadState);
|
||||||
|
}
|
||||||
|
secondThread.join();
|
||||||
|
EXPECT_EQ(protectedCounter, 2);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -163,7 +163,7 @@ public:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::lock_guard<SpinLock> guard(owner_.mutex_);
|
std::lock_guard guard(owner_.mutex_);
|
||||||
|
|
||||||
owner_.AssertCorrectUnsafe();
|
owner_.AssertCorrectUnsafe();
|
||||||
|
|
||||||
@@ -328,7 +328,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
ObjectFactoryStorage& owner_; // weak
|
ObjectFactoryStorage& owner_; // weak
|
||||||
std::unique_lock<SpinLock> guard_;
|
std::unique_lock<SpinLock<MutexThreadStateHandling::kIgnore>> guard_;
|
||||||
};
|
};
|
||||||
|
|
||||||
~ObjectFactoryStorage() {
|
~ObjectFactoryStorage() {
|
||||||
@@ -389,7 +389,7 @@ private:
|
|||||||
unique_ptr<Node> root_;
|
unique_ptr<Node> root_;
|
||||||
Node* last_ = nullptr;
|
Node* last_ = nullptr;
|
||||||
size_t size_ = 0;
|
size_t size_ = 0;
|
||||||
SpinLock mutex_;
|
SpinLock<MutexThreadStateHandling::kIgnore> mutex_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SimpleAllocator {
|
class SimpleAllocator {
|
||||||
|
|||||||
@@ -15,16 +15,18 @@ namespace mm {
|
|||||||
|
|
||||||
// Registry for all objects that have references outside of Kotlin.
|
// Registry for all objects that have references outside of Kotlin.
|
||||||
class StableRefRegistry : Pinned {
|
class StableRefRegistry : Pinned {
|
||||||
|
using Mutex = SpinLock<MutexThreadStateHandling::kIgnore>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
class ThreadQueue : public MultiSourceQueue<ObjHeader*>::Producer {
|
class ThreadQueue : public MultiSourceQueue<ObjHeader*, Mutex>::Producer {
|
||||||
public:
|
public:
|
||||||
explicit ThreadQueue(StableRefRegistry& registry) : Producer(registry.stableRefs_) {}
|
explicit ThreadQueue(StableRefRegistry& registry) : Producer(registry.stableRefs_) {}
|
||||||
// Do not add fields as this is just a wrapper and Producer does not have virtual destructor.
|
// Do not add fields as this is just a wrapper and Producer does not have virtual destructor.
|
||||||
};
|
};
|
||||||
|
|
||||||
using Iterable = MultiSourceQueue<ObjHeader*>::Iterable;
|
using Iterable = MultiSourceQueue<ObjHeader*, Mutex>::Iterable;
|
||||||
using Iterator = MultiSourceQueue<ObjHeader*>::Iterator;
|
using Iterator = MultiSourceQueue<ObjHeader*, Mutex>::Iterator;
|
||||||
using Node = MultiSourceQueue<ObjHeader*>::Node;
|
using Node = MultiSourceQueue<ObjHeader*, Mutex>::Node;
|
||||||
|
|
||||||
StableRefRegistry();
|
StableRefRegistry();
|
||||||
~StableRefRegistry();
|
~StableRefRegistry();
|
||||||
@@ -64,7 +66,7 @@ private:
|
|||||||
// before posting queue to the global registry)
|
// before posting queue to the global registry)
|
||||||
//
|
//
|
||||||
// TODO: Measure to understand, if this approach is problematic.
|
// TODO: Measure to understand, if this approach is problematic.
|
||||||
MultiSourceQueue<ObjHeader*> stableRefs_;
|
MultiSourceQueue<ObjHeader*, Mutex> stableRefs_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace mm
|
} // namespace mm
|
||||||
|
|||||||
Reference in New Issue
Block a user