[runtime] Introduce thread states

This commit is contained in:
Ilya Matveev
2020-12-07 19:15:19 +07:00
committed by Stanislav Erokhin
parent ceae6c5a3e
commit 6fb5e43f62
8 changed files with 207 additions and 1 deletions
@@ -3670,4 +3670,12 @@ void CheckGlobalsAccessible() {
ThrowIncorrectDereferenceException();
}
ALWAYS_INLINE RUNTIME_NOTHROW void Kotlin_mm_switchThreadStateNative() {
// no-op, used by the new MM only.
}
ALWAYS_INLINE RUNTIME_NOTHROW void Kotlin_mm_switchThreadStateRunnable() {
// no-op, used by the new MM only.
}
} // extern "C"
@@ -42,4 +42,6 @@ void EnsureDeclarationsEmitted() {
ensureUsed(FreezeSubgraph);
ensureUsed(FreezeSubgraph);
ensureUsed(CheckGlobalsAccessible);
ensureUsed(Kotlin_mm_switchThreadStateNative);
ensureUsed(Kotlin_mm_switchThreadStateRunnable);
}
@@ -268,6 +268,11 @@ void AdoptReferenceFromSharedVariable(ObjHeader* object);
void CheckGlobalsAccessible();
// Sets state of the current thread to NATIVE (used by the new MM).
ALWAYS_INLINE RUNTIME_NOTHROW void Kotlin_mm_switchThreadStateNative();
// Sets state of the current thread to RUNNABLE (used by the new MM).
ALWAYS_INLINE RUNTIME_NOTHROW void Kotlin_mm_switchThreadStateRunnable();
#ifdef __cplusplus
}
#endif
@@ -6,12 +6,14 @@
#ifndef RUNTIME_MM_THREAD_DATA_H
#define RUNTIME_MM_THREAD_DATA_H
#include <atomic>
#include <pthread.h>
#include "GlobalsRegistry.hpp"
#include "StableRefRegistry.hpp"
#include "ThreadLocalStorage.hpp"
#include "Utils.hpp"
#include "ThreadState.hpp"
namespace kotlin {
namespace mm {
@@ -21,7 +23,10 @@ namespace mm {
class ThreadData final : private Pinned {
public:
ThreadData(pthread_t threadId) noexcept :
threadId_(threadId), globalsThreadQueue_(GlobalsRegistry::Instance()), stableRefThreadQueue_(StableRefRegistry::Instance()) {}
threadId_(threadId),
globalsThreadQueue_(GlobalsRegistry::Instance()),
stableRefThreadQueue_(StableRefRegistry::Instance()),
state_(ThreadState::kRunnable) {}
~ThreadData() = default;
@@ -33,11 +38,16 @@ public:
StableRefRegistry::ThreadQueue& stableRefThreadQueue() noexcept { return stableRefThreadQueue_; }
ThreadState state() noexcept { return state_; }
ThreadState setState(ThreadState state) noexcept { return state_.exchange(state); }
private:
const pthread_t threadId_;
GlobalsRegistry::ThreadQueue globalsThreadQueue_;
ThreadLocalStorage tls_;
StableRefRegistry::ThreadQueue stableRefThreadQueue_;
std::atomic<ThreadState> state_;
};
} // namespace mm
@@ -0,0 +1,67 @@
/*
* Copyright 2010-2020 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 "ThreadState.hpp"
using namespace kotlin;
namespace {
ALWAYS_INLINE bool isStateSwitchAllowed(mm::ThreadState oldState, mm::ThreadState newState) noexcept {
return oldState != newState;
}
const char* stateToString(mm::ThreadState state) noexcept {
switch (state) {
case mm::ThreadState::kRunnable:
return "RUNNABLE";
case mm::ThreadState::kNative:
return "NATIVE";
}
}
std::string unexpectedStateMessage(mm::ThreadState expected, mm::ThreadState actual) noexcept {
return std::string("Unexpected thread state. Expected: ") + stateToString(expected)
+ ". Actual: " + stateToString(actual);
}
std::string illegalStateSwitchMessage(mm::ThreadState oldState, mm::ThreadState newState) noexcept {
return std::string("Illegal thread state switch. Old state: ") + stateToString(oldState)
+ ". New state: " + stateToString(newState);
}
} // namespace
// Switches the state of the current thread to `newState` and returns the previous state.
ALWAYS_INLINE mm::ThreadState mm::SwitchThreadState(ThreadData* threadData, ThreadState newState) noexcept {
auto oldState = threadData->setState(newState);
// TODO(perf): Mesaure the impact of this assert in debug and opt modes.
RuntimeAssert(isStateSwitchAllowed(oldState, newState),
illegalStateSwitchMessage(oldState, newState).c_str());
return oldState;
}
ALWAYS_INLINE void mm::AssertThreadState(ThreadData* threadData, ThreadState expected) noexcept {
auto actual = threadData->state();
RuntimeAssert(actual == expected, unexpectedStateMessage(expected, actual).c_str());
}
mm::ThreadStateGuard::ThreadStateGuard(ThreadData* threadData, ThreadState state) noexcept : threadData_(threadData) {
oldState_ = SwitchThreadState(threadData, state);
}
mm::ThreadStateGuard::~ThreadStateGuard() noexcept {
SwitchThreadState(threadData_, oldState_);
}
extern "C" ALWAYS_INLINE RUNTIME_NOTHROW void Kotlin_mm_switchThreadStateNative() {
mm::SwitchThreadState(mm::ThreadRegistry::Instance().CurrentThreadData(), mm::ThreadState::kNative);
}
extern "C" ALWAYS_INLINE RUNTIME_NOTHROW void Kotlin_mm_switchThreadStateRunnable() {
mm::SwitchThreadState(mm::ThreadRegistry::Instance().CurrentThreadData(), mm::ThreadState::kRunnable);
}
@@ -0,0 +1,36 @@
/*
* Copyright 2010-2020 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_STATE_H
#define RUNTIME_MM_THREAD_STATE_H
#include <Common.h>
#include <Utils.hpp>
namespace kotlin {
namespace mm {
enum class ThreadState {
kRunnable, kNative
};
// Switches the state of the current thread to `newState` and returns the previous thread state.
ALWAYS_INLINE ThreadState SwitchThreadState(ThreadData* threadData, ThreadState newState) noexcept;
ALWAYS_INLINE void AssertThreadState(ThreadData* threadData, ThreadState expected) noexcept;
class ThreadStateGuard final : private Pinned {
public:
explicit ThreadStateGuard(ThreadData* threadData, ThreadState state) noexcept;
~ThreadStateGuard() noexcept;
private:
ThreadData* threadData_;
ThreadState oldState_;
};
} // namespace mm
} // namespace kotlin
#endif // RUNTIME_MM_THREAD_STATE_H
@@ -0,0 +1,75 @@
/*
* Copyright 2010-2020 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 <thread>
#include "gtest/gtest.h"
#include "ThreadData.hpp"
#include "ThreadRegistry.hpp"
#include "ThreadState.hpp"
using namespace kotlin;
TEST(ThreadStateTest, StateSwitch) {
std::thread t([]() {
mm::ThreadRegistry::Instance().RegisterCurrentThread();
auto* threadData = mm::ThreadRegistry::Instance().CurrentThreadData();
auto initialState = threadData->state();
EXPECT_EQ(mm::ThreadState::kRunnable, initialState);
mm::ThreadState oldState = mm::SwitchThreadState(threadData, mm::ThreadState::kNative);
EXPECT_EQ(initialState, oldState);
EXPECT_EQ(mm::ThreadState::kNative, threadData->state());
// Check functions exported for the compiler too.
Kotlin_mm_switchThreadStateRunnable();
EXPECT_EQ(mm::ThreadState::kRunnable, threadData->state());
Kotlin_mm_switchThreadStateNative();
EXPECT_EQ(mm::ThreadState::kNative, threadData->state());
});
t.join();
}
TEST(ThreadStateTest, StateGuard) {
std::thread t([]() {
mm::ThreadRegistry::Instance().RegisterCurrentThread();
auto* threadData = mm::ThreadRegistry::Instance().CurrentThreadData();
auto initialState = threadData->state();
EXPECT_EQ(mm::ThreadState::kRunnable, initialState);
{
mm::ThreadStateGuard guard(threadData, mm::ThreadState::kNative);
EXPECT_EQ(mm::ThreadState::kNative, threadData->state());
}
EXPECT_EQ(initialState, threadData->state());
});
t.join();
}
TEST(ThreadStateDeathTest, StateAsserts) {
std::thread t([]() {
auto* threadData = mm::ThreadRegistry::Instance().RegisterCurrentThread()->Get();
EXPECT_DEATH(mm::AssertThreadState(threadData, mm::ThreadState::kNative),
"runtime assert: Unexpected thread state. Expected: NATIVE. Actual: RUNNABLE");
});
t.join();
}
TEST(ThreadStateDeathTest, IncorrectStateSwitch) {
std::thread t([]() {
auto* threadData = mm::ThreadRegistry::Instance().RegisterCurrentThread()->Get();
EXPECT_DEATH(mm::SwitchThreadState(threadData, kotlin::mm::ThreadState::kRunnable),
"runtime assert: Illegal thread state switch. Old state: RUNNABLE. New state: RUNNABLE");
EXPECT_DEATH(Kotlin_mm_switchThreadStateRunnable(),
"runtime assert: Illegal thread state switch. Old state: RUNNABLE. New state: RUNNABLE");
mm::SwitchThreadState(threadData, kotlin::mm::ThreadState::kNative);
EXPECT_DEATH(Kotlin_mm_switchThreadStateNative(),
"runtime assert: Illegal thread state switch. Old state: NATIVE. New state: NATIVE");
});
t.join();
}
@@ -8,5 +8,8 @@
int main(int argc, char** argv) {
testing::InitGoogleMock(&argc, argv);
// Use the `threadsafe` style to mitigate possible issues with multithreaded death tests.
// See more about death test styles: https://github.com/google/googletest/blob/master/googletest/docs/advanced.md#how-it-works
testing::FLAGS_gtest_death_test_style="threadsafe";
return RUN_ALL_TESTS();
}