[K/N][Runtime] Support suspending mutators in the new MM
This commit is contained in:
@@ -272,6 +272,12 @@ val assemble by tasks.registering {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val hostAssemble by tasks.registering {
|
||||||
|
dependsOn(tasks.withType(CompileToBitcode::class).matching {
|
||||||
|
it.outputGroup == "main" && it.target == hostName
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
val clean by tasks.registering {
|
val clean by tasks.registering {
|
||||||
doFirst {
|
doFirst {
|
||||||
delete(buildDir)
|
delete(buildDir)
|
||||||
|
|||||||
@@ -3733,6 +3733,10 @@ ALWAYS_INLINE void kotlin::AssertThreadState(MemoryState* thread, ThreadState ex
|
|||||||
// no-op, used by the new MM only.
|
// no-op, used by the new MM only.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ALWAYS_INLINE void kotlin::AssertThreadState(MemoryState* thread, std::initializer_list<ThreadState> expected) noexcept {
|
||||||
|
// no-op, used by the new MM only.
|
||||||
|
}
|
||||||
|
|
||||||
MemoryState* kotlin::mm::GetMemoryState() {
|
MemoryState* kotlin::mm::GetMemoryState() {
|
||||||
return ::memoryState;
|
return ::memoryState;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#ifndef RUNTIME_MEMORY_H
|
#ifndef RUNTIME_MEMORY_H
|
||||||
#define RUNTIME_MEMORY_H
|
#define RUNTIME_MEMORY_H
|
||||||
|
|
||||||
|
#include <ostream>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "KAssert.h"
|
#include "KAssert.h"
|
||||||
@@ -410,12 +411,17 @@ ALWAYS_INLINE ThreadState SwitchThreadState(MemoryState* thread, ThreadState new
|
|||||||
|
|
||||||
// Asserts that the given thread is in the given state.
|
// Asserts that the given thread is in the given state.
|
||||||
ALWAYS_INLINE void AssertThreadState(MemoryState* thread, ThreadState expected) noexcept;
|
ALWAYS_INLINE void AssertThreadState(MemoryState* thread, ThreadState expected) noexcept;
|
||||||
|
ALWAYS_INLINE void AssertThreadState(MemoryState* thread, std::initializer_list<ThreadState> expected) noexcept;
|
||||||
|
|
||||||
// Asserts that the current thread is in the the given state.
|
// Asserts that the current thread is in the the given state.
|
||||||
ALWAYS_INLINE inline void AssertThreadState(ThreadState expected) noexcept {
|
ALWAYS_INLINE inline void AssertThreadState(ThreadState expected) noexcept {
|
||||||
AssertThreadState(mm::GetMemoryState(), expected);
|
AssertThreadState(mm::GetMemoryState(), expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ALWAYS_INLINE inline void AssertThreadState(std::initializer_list<ThreadState> expected) noexcept {
|
||||||
|
AssertThreadState(mm::GetMemoryState(), expected);
|
||||||
|
}
|
||||||
|
|
||||||
// Scopely sets the given thread state for the given thread.
|
// Scopely sets the given thread state for the given thread.
|
||||||
class ThreadStateGuard final : private Pinned {
|
class ThreadStateGuard final : private Pinned {
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
#ifndef RUNTIME_UTILS_H
|
#ifndef RUNTIME_UTILS_H
|
||||||
#define RUNTIME_UTILS_H
|
#define RUNTIME_UTILS_H
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
namespace kotlin {
|
namespace kotlin {
|
||||||
|
|
||||||
// A helper for implementing classes with disabled copy constructor and copy assignment.
|
// A helper for implementing classes with disabled copy constructor and copy assignment.
|
||||||
@@ -52,6 +54,28 @@ protected:
|
|||||||
~Pinned() = default;
|
~Pinned() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// A helper that scopley assings a value to a variable. The variable will
|
||||||
|
// be set to its original value upon destruction of the AutoReset instance.
|
||||||
|
// Note that an AutoReset instance must have a shorter lifetime than
|
||||||
|
// the variable it works with to avoid invalid memory access.
|
||||||
|
template<typename T1, typename T2>
|
||||||
|
class AutoReset final : private Pinned {
|
||||||
|
static_assert(std::is_assignable<T1, T2>::value);
|
||||||
|
|
||||||
|
public:
|
||||||
|
AutoReset(T1* variable, T2 value) : variable_(variable), oldValue_(*variable) {
|
||||||
|
*variable_ = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
~AutoReset() {
|
||||||
|
*variable_ = oldValue_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
T1* variable_;
|
||||||
|
T2 oldValue_;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace kotlin
|
} // namespace kotlin
|
||||||
|
|
||||||
#endif // RUNTIME_UTILS_H
|
#endif // RUNTIME_UTILS_H
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include "ThreadLocalStorage.hpp"
|
#include "ThreadLocalStorage.hpp"
|
||||||
#include "Types.h"
|
#include "Types.h"
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
|
#include "ThreadSuspension.hpp"
|
||||||
|
|
||||||
struct ObjHeader;
|
struct ObjHeader;
|
||||||
|
|
||||||
@@ -32,9 +33,9 @@ public:
|
|||||||
threadId_(threadId),
|
threadId_(threadId),
|
||||||
globalsThreadQueue_(GlobalsRegistry::Instance()),
|
globalsThreadQueue_(GlobalsRegistry::Instance()),
|
||||||
stableRefThreadQueue_(StableRefRegistry::Instance()),
|
stableRefThreadQueue_(StableRefRegistry::Instance()),
|
||||||
state_(ThreadState::kRunnable),
|
|
||||||
gc_(GlobalData::Instance().gc()),
|
gc_(GlobalData::Instance().gc()),
|
||||||
objectFactoryThreadQueue_(GlobalData::Instance().objectFactory(), gc_) {}
|
objectFactoryThreadQueue_(GlobalData::Instance().objectFactory(), gc_),
|
||||||
|
suspensionData_(ThreadState::kRunnable) {}
|
||||||
|
|
||||||
~ThreadData() = default;
|
~ThreadData() = default;
|
||||||
|
|
||||||
@@ -46,9 +47,9 @@ public:
|
|||||||
|
|
||||||
StableRefRegistry::ThreadQueue& stableRefThreadQueue() noexcept { return stableRefThreadQueue_; }
|
StableRefRegistry::ThreadQueue& stableRefThreadQueue() noexcept { return stableRefThreadQueue_; }
|
||||||
|
|
||||||
ThreadState state() noexcept { return state_; }
|
ThreadState state() noexcept { return suspensionData_.state(); }
|
||||||
|
|
||||||
ThreadState setState(ThreadState state) noexcept { return state_.exchange(state); }
|
ThreadState setState(ThreadState state) noexcept { return suspensionData_.setState(state); }
|
||||||
|
|
||||||
ObjectFactory<gc::GC>::ThreadQueue& objectFactoryThreadQueue() noexcept { return objectFactoryThreadQueue_; }
|
ObjectFactory<gc::GC>::ThreadQueue& objectFactoryThreadQueue() noexcept { return objectFactoryThreadQueue_; }
|
||||||
|
|
||||||
@@ -58,6 +59,8 @@ public:
|
|||||||
|
|
||||||
gc::GC::ThreadData& gc() noexcept { return gc_; }
|
gc::GC::ThreadData& gc() noexcept { return gc_; }
|
||||||
|
|
||||||
|
ThreadSuspensionData& suspensionData() { return suspensionData_; }
|
||||||
|
|
||||||
void Publish() noexcept {
|
void Publish() noexcept {
|
||||||
// TODO: These use separate locks, which is inefficient.
|
// TODO: These use separate locks, which is inefficient.
|
||||||
globalsThreadQueue_.Publish();
|
globalsThreadQueue_.Publish();
|
||||||
@@ -76,11 +79,11 @@ private:
|
|||||||
GlobalsRegistry::ThreadQueue globalsThreadQueue_;
|
GlobalsRegistry::ThreadQueue globalsThreadQueue_;
|
||||||
ThreadLocalStorage tls_;
|
ThreadLocalStorage tls_;
|
||||||
StableRefRegistry::ThreadQueue stableRefThreadQueue_;
|
StableRefRegistry::ThreadQueue stableRefThreadQueue_;
|
||||||
std::atomic<ThreadState> state_;
|
|
||||||
ShadowStack shadowStack_;
|
ShadowStack shadowStack_;
|
||||||
gc::GC::ThreadData gc_;
|
gc::GC::ThreadData gc_;
|
||||||
ObjectFactory<gc::GC>::ThreadQueue objectFactoryThreadQueue_;
|
ObjectFactory<gc::GC>::ThreadQueue objectFactoryThreadQueue_;
|
||||||
KStdVector<std::pair<ObjHeader**, ObjHeader*>> initializingSingletons_;
|
KStdVector<std::pair<ObjHeader**, ObjHeader*>> initializingSingletons_;
|
||||||
|
ThreadSuspensionData suspensionData_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace mm
|
} // namespace mm
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
#include "ThreadData.hpp"
|
#include "ThreadData.hpp"
|
||||||
#include "ThreadState.hpp"
|
#include "ThreadState.hpp"
|
||||||
|
|
||||||
const char* kotlin::internal::stateToString(ThreadState state) noexcept {
|
const char* kotlin::ThreadStateName(ThreadState state) noexcept {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case ThreadState::kRunnable:
|
case ThreadState::kRunnable:
|
||||||
return "RUNNABLE";
|
return "RUNNABLE";
|
||||||
@@ -16,6 +16,18 @@ const char* kotlin::internal::stateToString(ThreadState state) noexcept {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string kotlin::internal::statesToString(std::initializer_list<ThreadState> states) noexcept {
|
||||||
|
std::string result = "{ ";
|
||||||
|
for (size_t i = 0; i < states.size(); i++) {
|
||||||
|
if (i != 0) {
|
||||||
|
result += ", ";
|
||||||
|
}
|
||||||
|
result += ThreadStateName(data(states)[i]);
|
||||||
|
}
|
||||||
|
result += " }";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
ALWAYS_INLINE ThreadState kotlin::SwitchThreadState(MemoryState* thread, ThreadState newState, bool reentrant) noexcept {
|
ALWAYS_INLINE ThreadState kotlin::SwitchThreadState(MemoryState* thread, ThreadState newState, bool reentrant) noexcept {
|
||||||
return SwitchThreadState(thread->GetThreadData(), newState, reentrant);
|
return SwitchThreadState(thread->GetThreadData(), newState, reentrant);
|
||||||
}
|
}
|
||||||
@@ -24,6 +36,10 @@ ALWAYS_INLINE void kotlin::AssertThreadState(MemoryState* thread, ThreadState ex
|
|||||||
AssertThreadState(thread->GetThreadData(), expected);
|
AssertThreadState(thread->GetThreadData(), expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ALWAYS_INLINE void kotlin::AssertThreadState(MemoryState* thread, std::initializer_list<ThreadState> expected) noexcept {
|
||||||
|
AssertThreadState(thread->GetThreadData(), expected);
|
||||||
|
}
|
||||||
|
|
||||||
ThreadState kotlin::GetThreadState(MemoryState* thread) noexcept {
|
ThreadState kotlin::GetThreadState(MemoryState* thread) noexcept {
|
||||||
return thread->GetThreadData()->state();
|
return thread->GetThreadData()->state();
|
||||||
}
|
}
|
||||||
@@ -6,10 +6,13 @@
|
|||||||
#ifndef RUNTIME_MM_THREAD_STATE_H
|
#ifndef RUNTIME_MM_THREAD_STATE_H
|
||||||
#define RUNTIME_MM_THREAD_STATE_H
|
#define RUNTIME_MM_THREAD_STATE_H
|
||||||
|
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
#include <Common.h>
|
#include <Common.h>
|
||||||
#include <Utils.hpp>
|
#include <Utils.hpp>
|
||||||
|
|
||||||
#include "ThreadData.hpp"
|
#include "ThreadData.hpp"
|
||||||
|
#include "ThreadSuspension.hpp"
|
||||||
|
|
||||||
namespace kotlin {
|
namespace kotlin {
|
||||||
|
|
||||||
@@ -19,17 +22,23 @@ ALWAYS_INLINE inline bool isStateSwitchAllowed(ThreadState oldState, ThreadState
|
|||||||
return oldState != newState || reentrant;
|
return oldState != newState || reentrant;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* stateToString(ThreadState state) noexcept;
|
std::string statesToString(std::initializer_list<ThreadState> states) noexcept;
|
||||||
|
|
||||||
} // namespace internal
|
} // namespace internal
|
||||||
|
|
||||||
|
const char* ThreadStateName(ThreadState state) noexcept;
|
||||||
|
|
||||||
|
inline std::ostream& operator<<(std::ostream& stream, ThreadState state) {
|
||||||
|
return stream << ThreadStateName(state);
|
||||||
|
}
|
||||||
|
|
||||||
// Switches the state of the given thread to `newState` and returns the previous thread state.
|
// Switches the state of the given thread to `newState` and returns the previous thread state.
|
||||||
ALWAYS_INLINE inline ThreadState SwitchThreadState(mm::ThreadData* threadData, ThreadState newState, bool reentrant = false) noexcept {
|
ALWAYS_INLINE inline ThreadState SwitchThreadState(mm::ThreadData* threadData, ThreadState newState, bool reentrant = false) noexcept {
|
||||||
auto oldState = threadData->setState(newState);
|
auto oldState = threadData->setState(newState);
|
||||||
// TODO(perf): Mesaure the impact of this assert in debug and opt modes.
|
// TODO(perf): Mesaure the impact of this assert in debug and opt modes.
|
||||||
RuntimeAssert(internal::isStateSwitchAllowed(oldState, newState, reentrant),
|
RuntimeAssert(internal::isStateSwitchAllowed(oldState, newState, reentrant),
|
||||||
"Illegal thread state switch. Old state: %s. New state: %s.",
|
"Illegal thread state switch. Old state: %s. New state: %s.",
|
||||||
internal::stateToString(oldState), internal::stateToString(newState));
|
ThreadStateName(oldState), ThreadStateName(newState));
|
||||||
return oldState;
|
return oldState;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +47,14 @@ ALWAYS_INLINE inline void AssertThreadState(mm::ThreadData* threadData, ThreadSt
|
|||||||
auto actual = threadData->state();
|
auto actual = threadData->state();
|
||||||
RuntimeAssert(actual == expected,
|
RuntimeAssert(actual == expected,
|
||||||
"Unexpected thread state. Expected: %s. Actual: %s.",
|
"Unexpected thread state. Expected: %s. Actual: %s.",
|
||||||
internal::stateToString(expected), internal::stateToString(actual));
|
ThreadStateName(expected), ThreadStateName(actual));
|
||||||
|
}
|
||||||
|
|
||||||
|
ALWAYS_INLINE inline void AssertThreadState(mm::ThreadData* threadData, std::initializer_list<ThreadState> expected) noexcept {
|
||||||
|
auto actual = threadData->state();
|
||||||
|
RuntimeAssert(std::any_of(expected.begin(), expected.end(), [actual](ThreadState expected) { return expected == actual; }),
|
||||||
|
"Unexpected thread state. Expected one of: %s. Actual: %s",
|
||||||
|
internal::statesToString(expected).c_str(), ThreadStateName(actual));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace kotlin
|
} // namespace kotlin
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ private:
|
|||||||
//static
|
//static
|
||||||
testing::MockFunction<int32_t(int32_t)>* ThreadStateTest::globalSomeFunctionMock = nullptr;
|
testing::MockFunction<int32_t(int32_t)>* ThreadStateTest::globalSomeFunctionMock = nullptr;
|
||||||
|
|
||||||
|
#define EXPECT_NO_DEATH(statement) \
|
||||||
|
do { EXPECT_EXIT({statement; exit(0);}, testing::ExitedWithCode(0), testing::_); } while(false)
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_F(ThreadStateTest, StateSwitchWithThreadData) {
|
TEST_F(ThreadStateTest, StateSwitchWithThreadData) {
|
||||||
@@ -148,13 +151,11 @@ TEST(ThreadStateDeathTest, StateAsserts) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ThreadStateDeathTest, IncorrectStateSwitch) {
|
TEST(ThreadStateDeathTest, IncorrectStateSwitchWithDifferentFunctions) {
|
||||||
RunInNewThread([](MemoryState* memoryState) {
|
RunInNewThread([](MemoryState* memoryState) {
|
||||||
auto* threadData = memoryState->GetThreadData();
|
auto* threadData = memoryState->GetThreadData();
|
||||||
EXPECT_DEATH(SwitchThreadState(memoryState, ThreadState::kRunnable),
|
EXPECT_DEATH(SwitchThreadState(memoryState, ThreadState::kRunnable),
|
||||||
"runtime assert: Illegal thread state switch. Old state: RUNNABLE. New state: RUNNABLE");
|
"runtime assert: Illegal thread state switch. Old state: RUNNABLE. New state: RUNNABLE");
|
||||||
EXPECT_DEATH(SwitchThreadState(threadData, ThreadState::kRunnable),
|
|
||||||
"runtime assert: Illegal thread state switch. Old state: RUNNABLE. New state: RUNNABLE");
|
|
||||||
|
|
||||||
EXPECT_DEATH(Kotlin_mm_switchThreadStateRunnable(),
|
EXPECT_DEATH(Kotlin_mm_switchThreadStateRunnable(),
|
||||||
"runtime assert: Illegal thread state switch. Old state: RUNNABLE. New state: RUNNABLE");
|
"runtime assert: Illegal thread state switch. Old state: RUNNABLE. New state: RUNNABLE");
|
||||||
@@ -165,6 +166,24 @@ TEST(ThreadStateDeathTest, IncorrectStateSwitch) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(ThreadStateDeathTest, StateSwitchCorrectness) {
|
||||||
|
mm::ThreadData threadData(pthread_self());
|
||||||
|
|
||||||
|
// Allowed state switches: runnable <-> native
|
||||||
|
threadData.setState(ThreadState::kRunnable);
|
||||||
|
ASSERT_EQ(threadData.state(), ThreadState::kRunnable);
|
||||||
|
EXPECT_DEATH(SwitchThreadState(&threadData, ThreadState::kRunnable),
|
||||||
|
"runtime assert: Illegal thread state switch. Old state: RUNNABLE. New state: RUNNABLE");
|
||||||
|
// Each EXPECT_NO_DEATH is executed in a fork process, so the global state of the test is not affected.
|
||||||
|
EXPECT_NO_DEATH(SwitchThreadState(&threadData, ThreadState::kNative));
|
||||||
|
|
||||||
|
threadData.setState(ThreadState::kNative);
|
||||||
|
ASSERT_EQ(threadData.state(), ThreadState::kNative);
|
||||||
|
EXPECT_NO_DEATH(SwitchThreadState(&threadData, ThreadState::kRunnable));
|
||||||
|
EXPECT_DEATH(SwitchThreadState(&threadData, ThreadState::kNative),
|
||||||
|
"runtime assert: Illegal thread state switch. Old state: NATIVE. New state: NATIVE");
|
||||||
|
}
|
||||||
|
|
||||||
TEST(ThreadStateDeathTest, ReentrantStateSwitch) {
|
TEST(ThreadStateDeathTest, ReentrantStateSwitch) {
|
||||||
RunInNewThread([](MemoryState* memoryState) {
|
RunInNewThread([](MemoryState* memoryState) {
|
||||||
auto* threadData = memoryState->GetThreadData();
|
auto* threadData = memoryState->GetThreadData();
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* 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 "ThreadData.hpp"
|
||||||
|
#include "ThreadSuspension.hpp"
|
||||||
|
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <thread>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool isSuspendedOrNative(kotlin::mm::ThreadData& thread) noexcept {
|
||||||
|
auto& suspensionData = thread.suspensionData();
|
||||||
|
return suspensionData.suspended() || suspensionData.state() == kotlin::ThreadState::kNative;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename F>
|
||||||
|
bool allThreads(F predicate) noexcept {
|
||||||
|
auto& threadRegistry = kotlin::mm::ThreadRegistry::Instance();
|
||||||
|
auto* currentThread = threadRegistry.CurrentThreadData();
|
||||||
|
kotlin::mm::ThreadRegistry::Iterable threads = kotlin::mm::ThreadRegistry::Instance().Iter();
|
||||||
|
for (auto& thread : threads) {
|
||||||
|
// Handle if suspension was initiated by the mutator thread.
|
||||||
|
if (&thread == currentThread)
|
||||||
|
continue;
|
||||||
|
if (!predicate(thread)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void yield() noexcept {
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::atomic<bool> gSuspensionRequested = false;
|
||||||
|
THREAD_LOCAL_VARIABLE bool gSuspensionRequestedByCurrentThread = false;
|
||||||
|
std::mutex gSuspensionMutex;
|
||||||
|
std::condition_variable gSuspendsionCondVar;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool kotlin::mm::ThreadSuspensionData::suspendIfRequested() noexcept {
|
||||||
|
if (IsThreadSuspensionRequested()) {
|
||||||
|
std::unique_lock lock(gSuspensionMutex);
|
||||||
|
if (IsThreadSuspensionRequested()) {
|
||||||
|
AutoReset scopedAssign(&suspended_, true);
|
||||||
|
gSuspendsionCondVar.wait(lock, []() { return !IsThreadSuspensionRequested(); });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool kotlin::mm::IsThreadSuspensionRequested() noexcept {
|
||||||
|
// TODO: Consider using a more relaxed memory order.
|
||||||
|
return gSuspensionRequested.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool kotlin::mm::SuspendThreads() noexcept {
|
||||||
|
RuntimeAssert(gSuspensionRequestedByCurrentThread == false, "Current thread already suspended threads.");
|
||||||
|
{
|
||||||
|
std::unique_lock lock(gSuspensionMutex);
|
||||||
|
bool actual = false;
|
||||||
|
gSuspensionRequested.compare_exchange_strong(actual, true);
|
||||||
|
if (actual) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gSuspensionRequestedByCurrentThread = true;
|
||||||
|
|
||||||
|
// Spin wating for threads to suspend. Ignore Native threads.
|
||||||
|
while(!allThreads(isSuspendedOrNative)) {
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void kotlin::mm::ResumeThreads() noexcept {
|
||||||
|
// From the std::condition_variable docs:
|
||||||
|
// Even if the shared variable is atomic, it must be modified under
|
||||||
|
// the mutex in order to correctly publish the modification to the waiting thread.
|
||||||
|
// https://en.cppreference.com/w/cpp/thread/condition_variable
|
||||||
|
{
|
||||||
|
std::unique_lock lock(gSuspensionMutex);
|
||||||
|
gSuspensionRequested = false;
|
||||||
|
}
|
||||||
|
gSuspensionRequestedByCurrentThread = false;
|
||||||
|
gSuspendsionCondVar.notify_all();
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef RUNTIME_MM_THREAD_SUSPENSION_UTILS_H
|
||||||
|
#define RUNTIME_MM_THREAD_SUSPENSION_UTILS_H
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
#include "Memory.h"
|
||||||
|
|
||||||
|
namespace kotlin {
|
||||||
|
namespace mm {
|
||||||
|
|
||||||
|
class ThreadSuspensionData : private Pinned {
|
||||||
|
public:
|
||||||
|
explicit ThreadSuspensionData(ThreadState initialState) noexcept : state_(initialState), suspended_(false) {}
|
||||||
|
|
||||||
|
~ThreadSuspensionData() = default;
|
||||||
|
|
||||||
|
ThreadState state() noexcept { return state_; }
|
||||||
|
|
||||||
|
ThreadState setState(ThreadState newState) noexcept {
|
||||||
|
ThreadState oldState = state_.exchange(newState);
|
||||||
|
if (oldState == ThreadState::kNative && newState == ThreadState::kRunnable) {
|
||||||
|
suspendIfRequested();
|
||||||
|
}
|
||||||
|
return oldState;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool suspended() noexcept { return suspended_; }
|
||||||
|
|
||||||
|
bool suspendIfRequested() noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::atomic<ThreadState> state_;
|
||||||
|
std::atomic<bool> suspended_;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool IsThreadSuspensionRequested() noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suspends all threads registered in ThreadRegistry except threads that are in the Native state.
|
||||||
|
* Blocks until all such threads are suspended. Threads that are in the Native state on the moment
|
||||||
|
* of this call will be suspended on exit from the Native state.
|
||||||
|
* Returns false if some other thread has suspended the threads.
|
||||||
|
*/
|
||||||
|
bool SuspendThreads() noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resumes all threads registered in ThreadRegistry that were suspended by the SuspendThreads call.
|
||||||
|
* Does not wait until all such threads are actually resumed.
|
||||||
|
*/
|
||||||
|
void ResumeThreads() noexcept;
|
||||||
|
|
||||||
|
} // namespace mm
|
||||||
|
} // namespace kotlin
|
||||||
|
|
||||||
|
#endif // RUNTIME_MM_THREAD_SUSPENSION_UTILS_H
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
/*
|
||||||
|
* 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 "MemoryPrivate.hpp"
|
||||||
|
#include "ThreadSuspension.hpp"
|
||||||
|
#include "ThreadState.hpp"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
#include <TestSupport.hpp>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace kotlin;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
#ifdef KONAN_WINDOWS
|
||||||
|
constexpr size_t kDefaultIterations = 1000;
|
||||||
|
constexpr size_t kDefaultReportingStep = 100;
|
||||||
|
#else
|
||||||
|
constexpr size_t kDefaultIterations = 10000;
|
||||||
|
constexpr size_t kDefaultReportingStep = 1000;
|
||||||
|
#endif // #ifdef KONAN_WINDOWS
|
||||||
|
|
||||||
|
KStdVector<mm::ThreadData*> collectThreadData() {
|
||||||
|
KStdVector<mm::ThreadData*> result;
|
||||||
|
auto iter = mm::ThreadRegistry::Instance().Iter();
|
||||||
|
for (auto& thread : iter) {
|
||||||
|
result.push_back(&thread);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename F>
|
||||||
|
KStdVector<T> collectFromThreadData(F extractFunction) {
|
||||||
|
KStdVector<T> result;
|
||||||
|
auto threadData = collectThreadData();
|
||||||
|
std::transform(threadData.begin(), threadData.end(), std::back_inserter(result), extractFunction);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
KStdVector<bool> collectSuspended() {
|
||||||
|
return collectFromThreadData<bool>(
|
||||||
|
[](mm::ThreadData* threadData) { return threadData->suspensionData().suspended(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void reportProgress(size_t currentIteration, size_t totalIterations) {
|
||||||
|
if (currentIteration % kDefaultReportingStep == 0) {
|
||||||
|
std::cout << "Iteration: " << currentIteration << " of " << totalIterations << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
class ThreadSuspensionTest : public ::testing::Test {
|
||||||
|
public:
|
||||||
|
~ThreadSuspensionTest() {
|
||||||
|
canStart = true;
|
||||||
|
shouldStop = true;
|
||||||
|
for (auto& thread : threads) {
|
||||||
|
if (thread.joinable()) thread.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr size_t kThreadCount = kDefaultThreadCount;
|
||||||
|
static constexpr size_t kIterations = kDefaultIterations;
|
||||||
|
|
||||||
|
KStdVector<std::thread> threads;
|
||||||
|
std::array<std::atomic<bool>, kThreadCount> ready{false};
|
||||||
|
std::atomic<bool> canStart{false};
|
||||||
|
std::atomic<bool> shouldStop{false};
|
||||||
|
|
||||||
|
void waitUntilCanStart(size_t threadNumber) {
|
||||||
|
ready[threadNumber] = true;
|
||||||
|
while(!canStart) {
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
ready[threadNumber] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void waitUntilThreadsAreReady() {
|
||||||
|
canStart = false;
|
||||||
|
while (!std::all_of(ready.begin(), ready.end(), [](bool it) { return it; })) {
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(ThreadSuspensionTest, SimpleStartStop) {
|
||||||
|
ASSERT_THAT(collectThreadData(), testing::IsEmpty());
|
||||||
|
for (size_t i = 0; i < kThreadCount; i++) {
|
||||||
|
threads.emplace_back([this, i]() {
|
||||||
|
ScopedMemoryInit init;
|
||||||
|
auto& suspensionData = init.memoryState()->GetThreadData()->suspensionData();
|
||||||
|
EXPECT_EQ(mm::IsThreadSuspensionRequested(), false);
|
||||||
|
|
||||||
|
while(!shouldStop) {
|
||||||
|
waitUntilCanStart(i);
|
||||||
|
|
||||||
|
EXPECT_FALSE(suspensionData.suspended());
|
||||||
|
suspensionData.suspendIfRequested();
|
||||||
|
EXPECT_FALSE(suspensionData.suspended());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
waitUntilThreadsAreReady();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < kIterations; i++) {
|
||||||
|
reportProgress(i, kIterations);
|
||||||
|
canStart = true;
|
||||||
|
|
||||||
|
mm::SuspendThreads();
|
||||||
|
auto suspended = collectSuspended();
|
||||||
|
EXPECT_THAT(suspended, testing::Each(true));
|
||||||
|
EXPECT_EQ(mm::IsThreadSuspensionRequested(), true);
|
||||||
|
|
||||||
|
mm::ResumeThreads();
|
||||||
|
|
||||||
|
// Wait for threads to run and sync for the next iteration
|
||||||
|
waitUntilThreadsAreReady();
|
||||||
|
|
||||||
|
suspended = collectSuspended();
|
||||||
|
EXPECT_THAT(suspended, testing::Each(false));
|
||||||
|
EXPECT_EQ(mm::IsThreadSuspensionRequested(), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST_F(ThreadSuspensionTest, SwitchStateToNative) {
|
||||||
|
ASSERT_THAT(collectThreadData(), testing::IsEmpty());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < kThreadCount; i++) {
|
||||||
|
threads.emplace_back([this, i]() {
|
||||||
|
ScopedMemoryInit init;
|
||||||
|
auto* threadData = init.memoryState()->GetThreadData();
|
||||||
|
EXPECT_EQ(mm::IsThreadSuspensionRequested(), false);
|
||||||
|
|
||||||
|
while(!shouldStop) {
|
||||||
|
waitUntilCanStart(i);
|
||||||
|
|
||||||
|
EXPECT_EQ(threadData->state(), ThreadState::kRunnable);
|
||||||
|
SwitchThreadState(threadData, ThreadState::kNative);
|
||||||
|
EXPECT_EQ(threadData->state(), ThreadState::kNative);
|
||||||
|
SwitchThreadState(threadData, ThreadState::kRunnable);
|
||||||
|
EXPECT_EQ(threadData->state(), ThreadState::kRunnable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
waitUntilThreadsAreReady();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < kIterations; i++) {
|
||||||
|
reportProgress(i, kIterations);
|
||||||
|
canStart = true;
|
||||||
|
|
||||||
|
mm::SuspendThreads();
|
||||||
|
EXPECT_EQ(mm::IsThreadSuspensionRequested(), true);
|
||||||
|
|
||||||
|
mm::ResumeThreads();
|
||||||
|
EXPECT_EQ(mm::IsThreadSuspensionRequested(), false);
|
||||||
|
|
||||||
|
// Sync for the next iteration.
|
||||||
|
waitUntilThreadsAreReady();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ThreadSuspensionTest, ConcurrentSuspend) {
|
||||||
|
ASSERT_THAT(collectThreadData(), testing::IsEmpty());
|
||||||
|
std::atomic<size_t> successCount = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < kThreadCount; i++) {
|
||||||
|
threads.emplace_back([this, i, &successCount]() {
|
||||||
|
ScopedMemoryInit init;
|
||||||
|
auto* currentThreadData = init.memoryState()->GetThreadData();
|
||||||
|
EXPECT_EQ(mm::IsThreadSuspensionRequested(), false);
|
||||||
|
|
||||||
|
// Sync with other threads.
|
||||||
|
ready[i] = true;
|
||||||
|
waitUntilThreadsAreReady();
|
||||||
|
|
||||||
|
bool success = mm::SuspendThreads();
|
||||||
|
if (success) {
|
||||||
|
successCount++;
|
||||||
|
auto allThreadData = collectThreadData();
|
||||||
|
auto isCurrentOrSuspended = [currentThreadData](mm::ThreadData* data) {
|
||||||
|
return data == currentThreadData || data->suspensionData().suspended();
|
||||||
|
};
|
||||||
|
EXPECT_THAT(allThreadData, testing::Each(testing::Truly(isCurrentOrSuspended)));
|
||||||
|
EXPECT_FALSE(currentThreadData->suspensionData().suspended());
|
||||||
|
mm::ResumeThreads();
|
||||||
|
} else {
|
||||||
|
EXPECT_TRUE(mm::IsThreadSuspensionRequested());
|
||||||
|
currentThreadData->suspensionData().suspendIfRequested();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (auto& thread : threads) {
|
||||||
|
thread.join();
|
||||||
|
}
|
||||||
|
EXPECT_EQ(successCount, 1u);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user