[runtime] Introduce thread states
This commit is contained in:
committed by
Stanislav Erokhin
parent
ceae6c5a3e
commit
6fb5e43f62
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user