diff --git a/kotlin-native/runtime/src/gc/cms/cpp/ConcurrentMarkAndSweep.cpp b/kotlin-native/runtime/src/gc/cms/cpp/ConcurrentMarkAndSweep.cpp index 55981a07aa7..a9d9d0ce562 100644 --- a/kotlin-native/runtime/src/gc/cms/cpp/ConcurrentMarkAndSweep.cpp +++ b/kotlin-native/runtime/src/gc/cms/cpp/ConcurrentMarkAndSweep.cpp @@ -91,7 +91,7 @@ gc::ConcurrentMarkAndSweep::ConcurrentMarkAndSweep( NativeOrUnregisteredThreadGuard guard(/* reentrant = */ true); state_.schedule(); }); - gcThread_ = std::thread([this] { + gcThread_ = ScopedThread(ScopedThread::attributes().name("GC thread"), [this] { while (true) { auto epoch = state_.waitScheduled(); if (epoch.has_value()) { @@ -106,7 +106,6 @@ gc::ConcurrentMarkAndSweep::ConcurrentMarkAndSweep( gc::ConcurrentMarkAndSweep::~ConcurrentMarkAndSweep() { state_.shutdown(); - gcThread_.join(); } void gc::ConcurrentMarkAndSweep::StartFinalizerThreadIfNeeded() noexcept { diff --git a/kotlin-native/runtime/src/gc/cms/cpp/ConcurrentMarkAndSweep.hpp b/kotlin-native/runtime/src/gc/cms/cpp/ConcurrentMarkAndSweep.hpp index c0baa5b4f6d..12c88551ef2 100644 --- a/kotlin-native/runtime/src/gc/cms/cpp/ConcurrentMarkAndSweep.hpp +++ b/kotlin-native/runtime/src/gc/cms/cpp/ConcurrentMarkAndSweep.hpp @@ -10,6 +10,7 @@ #include "Allocator.hpp" #include "GCScheduler.hpp" #include "ObjectFactory.hpp" +#include "ScopedThread.hpp" #include "Types.h" #include "Utils.hpp" #include "GCState.hpp" @@ -85,7 +86,7 @@ private: uint64_t lastGCTimestampUs_ = 0; GCStateHolder state_; - std::thread gcThread_; + ScopedThread gcThread_; KStdUniquePtr finalizerProcessor_; }; diff --git a/kotlin-native/runtime/src/gc/cms/cpp/FinalizerProcessor.cpp b/kotlin-native/runtime/src/gc/cms/cpp/FinalizerProcessor.cpp index 74e8b68bab6..ce678319e71 100644 --- a/kotlin-native/runtime/src/gc/cms/cpp/FinalizerProcessor.cpp +++ b/kotlin-native/runtime/src/gc/cms/cpp/FinalizerProcessor.cpp @@ -7,12 +7,10 @@ #include "ObjectFactory.hpp" #include "Runtime.h" -#include - void kotlin::gc::FinalizerProcessor::StartFinalizerThreadIfNone() noexcept { if (finalizerThread_.joinable()) return; - finalizerThread_ = std::thread([this] { + finalizerThread_ = ScopedThread(ScopedThread::attributes().name("GC finalizer processor"), [this] { Kotlin_initRuntimeIfNeeded(); { std::unique_lock guard(initializedMutex_); diff --git a/kotlin-native/runtime/src/gc/cms/cpp/FinalizerProcessor.hpp b/kotlin-native/runtime/src/gc/cms/cpp/FinalizerProcessor.hpp index c817611a17d..85cfca0e89c 100644 --- a/kotlin-native/runtime/src/gc/cms/cpp/FinalizerProcessor.hpp +++ b/kotlin-native/runtime/src/gc/cms/cpp/FinalizerProcessor.hpp @@ -5,9 +5,10 @@ #pragma once -#include "ObjectFactory.hpp" #include "ConcurrentMarkAndSweep.hpp" #include "GCState.hpp" +#include "ObjectFactory.hpp" +#include "ScopedThread.hpp" namespace kotlin::gc { @@ -25,7 +26,7 @@ public: ~FinalizerProcessor(); private: - std::thread finalizerThread_; + ScopedThread finalizerThread_; Queue finalizerQueue_; std::condition_variable finalizerQueueCondVar_; std::mutex finalizerQueueMutex_; diff --git a/kotlin-native/runtime/src/gc/common/cpp/GCScheduler.cpp b/kotlin-native/runtime/src/gc/common/cpp/GCScheduler.cpp index 1e774a76753..144510ba1c0 100644 --- a/kotlin-native/runtime/src/gc/common/cpp/GCScheduler.cpp +++ b/kotlin-native/runtime/src/gc/common/cpp/GCScheduler.cpp @@ -11,10 +11,13 @@ #include "GlobalData.hpp" #include "KAssert.h" #include "Porting.h" -#include "RepeatedTimer.hpp" #include "ThreadRegistry.hpp" #include "ThreadData.hpp" +#ifndef KONAN_NO_THREADS +#include "RepeatedTimer.hpp" +#endif + using namespace kotlin; namespace { @@ -91,6 +94,8 @@ class GCEmptySchedulerData : public gc::GCSchedulerData { void UpdateAliveSetBytes(size_t bytes) noexcept override {} }; +#ifndef KONAN_NO_THREADS + class GCSchedulerDataWithTimer : public gc::GCSchedulerData { public: GCSchedulerDataWithTimer( @@ -101,7 +106,7 @@ public: heapGrowthController_(config), regularIntervalPacer_(config, currentTimeProvider), scheduleGC_(std::move(scheduleGC)), - timer_(config_.regularGcInterval(), [this]() { + timer_("GC Timer thread", config_.regularGcInterval(), [this]() { if (regularIntervalPacer_.NeedsGC()) { scheduleGC_(); } @@ -130,6 +135,8 @@ private: RepeatedTimer timer_; }; +#endif // !KONAN_NO_THREADS + class GCSchedulerDataOnSafepoints : public gc::GCSchedulerData { public: GCSchedulerDataOnSafepoints( @@ -190,8 +197,12 @@ KStdUniquePtr kotlin::gc::internal::MakeGCSchedulerData( RuntimeLogDebug({kTagGC}, "GC scheduler disabled"); return ::make_unique(); case SchedulerType::kWithTimer: +#ifndef KONAN_NO_THREADS RuntimeLogDebug({kTagGC}, "Initializing timer-based GC scheduler"); return ::make_unique(config, std::move(scheduleGC), std::move(currentTimeProvider)); +#else + RuntimeFail("GC scheduler with timer is not supported on this platform"); +#endif case SchedulerType::kOnSafepoints: RuntimeLogDebug({kTagGC}, "Initializing safe-point-based GC scheduler"); return ::make_unique(config, std::move(scheduleGC), std::move(currentTimeProvider)); diff --git a/kotlin-native/runtime/src/main/cpp/ExceptionsTest.cpp b/kotlin-native/runtime/src/main/cpp/ExceptionsTest.cpp index 044231252ce..965a8debcf4 100644 --- a/kotlin-native/runtime/src/main/cpp/ExceptionsTest.cpp +++ b/kotlin-native/runtime/src/main/cpp/ExceptionsTest.cpp @@ -13,6 +13,7 @@ #include "Memory.h" #include "ObjectTestSupport.hpp" +#include "ScopedThread.hpp" #include "TestSupportCompilerGenerated.hpp" #include "TestSupport.hpp" @@ -417,7 +418,7 @@ TEST(TerminationThreadStateDeathTest, UnhandledKotlinExceptionInRunnableState) { // Do not use RunInNewThread because the termination handler will check initiliazation // of the whole runtime while RunInNewThread initializes the memory only. - std::thread thread([]() { + ScopedThread([]() { Kotlin_initRuntimeIfNeeded(); SwitchThreadState(mm::GetMemoryState(), ThreadState::kRunnable); @@ -425,7 +426,6 @@ TEST(TerminationThreadStateDeathTest, UnhandledKotlinExceptionInRunnableState) { ObjHeader exception{}; ExceptionObjHolder::Throw(&exception); }); - thread.join(); }; EXPECT_DEATH(testBlock(), AllOf(ASSERTS_PASSED, KOTLIN_HANDLER_RAN, Not(NATIVE_HANDLER_RAN))); @@ -441,14 +441,13 @@ TEST(TerminationThreadStateDeathTest, UnhandledKotlinExceptionInNativeState) { // Do not use RunInNewThread because the termination handler will check initiliazation // of the whole runtime while RunInNewThread initializes the memory only. - std::thread thread([]() { + ScopedThread([]() { Kotlin_initRuntimeIfNeeded(); loggingAssert(GetThreadState() == ThreadState::kNative, "Expected kNative thread state before throwing"); ObjHeader exception{}; ExceptionObjHolder::Throw(&exception); }); - thread.join(); }; EXPECT_DEATH(testBlock(), AllOf(ASSERTS_PASSED, KOTLIN_HANDLER_RAN, Not(NATIVE_HANDLER_RAN))); @@ -461,7 +460,7 @@ TEST(TerminationThreadStateDeathTest, UnhandledKotlinExceptionInForeignThread) { // It is possible if a Kotlin exception thrown by a Kotlin callback is re-thrown in // another thread which is not attached to the Kotlin runtime at all. - std::thread foreignThread([]() { + ScopedThread([]() { loggingAssert(!mm::IsCurrentThreadRegistered(), "Expected unregistered thread before throwing"); auto future = std::async(std::launch::async, []() { @@ -476,7 +475,6 @@ TEST(TerminationThreadStateDeathTest, UnhandledKotlinExceptionInForeignThread) { // Re-throw the Kotlin exception in a foreign thread. future.get(); }); - foreignThread.join(); }; EXPECT_DEATH(testBlock(), AllOf(ASSERTS_PASSED, KOTLIN_HANDLER_RAN, Not(NATIVE_HANDLER_RAN))); @@ -503,11 +501,10 @@ TEST(TerminationThreadStateDeathTest, UnhandledForeignExceptionInForeignThread) auto testBlock = []() { setupMocks(/* expectRegisteredThread = */ false); - std::thread foreignThread([]() { + ScopedThread([]() { loggingAssert(!mm::IsCurrentThreadRegistered(), "Expected unregistered thread before throwing"); throw std::runtime_error("Foreign exception"); }); - foreignThread.join(); }; EXPECT_DEATH(testBlock(), AllOf(ASSERTS_PASSED, NATIVE_HANDLER_RAN, Not(KOTLIN_HANDLER_RAN))); diff --git a/kotlin-native/runtime/src/main/cpp/MultiSourceQueueTest.cpp b/kotlin-native/runtime/src/main/cpp/MultiSourceQueueTest.cpp index dc9f8810a43..c16c00778ef 100644 --- a/kotlin-native/runtime/src/main/cpp/MultiSourceQueueTest.cpp +++ b/kotlin-native/runtime/src/main/cpp/MultiSourceQueueTest.cpp @@ -6,11 +6,11 @@ #include "MultiSourceQueue.hpp" #include -#include #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "ScopedThread.hpp" #include "TestSupport.hpp" #include "Types.h" @@ -193,7 +193,7 @@ TEST(MultiSourceQueueTest, ConcurrentPublish) { constexpr int kThreadCount = kDefaultThreadCount; std::atomic canStart(false); std::atomic readyCount(0); - KStdVector threads; + KStdVector threads; KStdVector expected; for (int i = 0; i < kThreadCount; ++i) { @@ -211,9 +211,7 @@ TEST(MultiSourceQueueTest, ConcurrentPublish) { while (readyCount < kThreadCount) { } canStart = true; - for (auto& t : threads) { - t.join(); - } + threads.clear(); auto actual = Collect(queue); EXPECT_THAT(actual, testing::UnorderedElementsAreArray(expected)); @@ -237,7 +235,7 @@ TEST(MultiSourceQueueTest, IterWhileConcurrentPublish) { std::atomic canStart(false); std::atomic readyCount(0); std::atomic startedCount(0); - KStdVector threads; + KStdVector threads; for (int i = 0; i < kThreadCount; ++i) { int j = i + kStartCount; expectedAfter.push_back(j); @@ -266,9 +264,7 @@ TEST(MultiSourceQueueTest, IterWhileConcurrentPublish) { } } - for (auto& t : threads) { - t.join(); - } + threads.clear(); EXPECT_THAT(actualBefore, testing::ElementsAreArray(expectedBefore)); @@ -283,7 +279,7 @@ TEST(MultiSourceQueueTest, ConcurrentPublishAndApplyDeletions) { std::atomic canStart(false); std::atomic readyCount(0); std::atomic startedCount(0); - KStdVector threads; + KStdVector threads; for (int i = 0; i < kThreadCount; ++i) { threads.emplace_back([&queue, i, &canStart, &readyCount, &startedCount]() { IntQueue::Producer producer(queue); @@ -306,9 +302,7 @@ TEST(MultiSourceQueueTest, ConcurrentPublishAndApplyDeletions) { queue.ApplyDeletions(); - for (auto& t : threads) { - t.join(); - } + threads.clear(); // We do not know which elements were deleted at this point. Expecting not to crash by this point. diff --git a/kotlin-native/runtime/src/main/cpp/MutexTest.cpp b/kotlin-native/runtime/src/main/cpp/MutexTest.cpp index 8172a29711d..9f4674d0686 100644 --- a/kotlin-native/runtime/src/main/cpp/MutexTest.cpp +++ b/kotlin-native/runtime/src/main/cpp/MutexTest.cpp @@ -6,9 +6,10 @@ #include "Mutex.hpp" #include -#include #include "gtest/gtest.h" + +#include "ScopedThread.hpp" #include "TestSupport.hpp" using namespace kotlin; @@ -23,13 +24,13 @@ TYPED_TEST(MutexTest, SmokeDetachedThread) { using LockUnderTest = TypeParam; LockUnderTest lock; - std::thread secondThread; + ScopedThread secondThread; std::atomic started = false; std::atomic protectedCounter = 0; { std::unique_lock guard1(lock); - secondThread = std::thread([&lock, &started, &protectedCounter]() { + secondThread = ScopedThread([&lock, &started, &protectedCounter]() { started = true; std::unique_lock guard2(lock); protectedCounter++; diff --git a/kotlin-native/runtime/src/main/cpp/RepeatedTimer.hpp b/kotlin-native/runtime/src/main/cpp/RepeatedTimer.hpp index e017c1f47f3..51c1264675b 100644 --- a/kotlin-native/runtime/src/main/cpp/RepeatedTimer.hpp +++ b/kotlin-native/runtime/src/main/cpp/RepeatedTimer.hpp @@ -3,15 +3,17 @@ * that can be found in the LICENSE file. */ -#ifndef RUNTIME_REPEATED_TIMER_H -#define RUNTIME_REPEATED_TIMER_H +#pragma once + +#ifndef KONAN_NO_THREADS #include #include #include -#include +#include #include "KAssert.h" +#include "ScopedThread.hpp" #include "Utils.hpp" namespace kotlin { @@ -19,8 +21,13 @@ namespace kotlin { class RepeatedTimer : private Pinned { public: template - RepeatedTimer(std::chrono::duration interval, F f) noexcept : - thread_([this, interval, f]() noexcept { Run(interval, f); }) {} + RepeatedTimer(std::string_view name, std::chrono::duration interval, F&& f) noexcept : + thread_(ScopedThread::attributes().name(name), &RepeatedTimer::Run, this, std::move(interval), std::forward(f)) { + } + + template + RepeatedTimer(std::chrono::duration interval, F&& f) noexcept : + RepeatedTimer("Timer thread", interval, std::forward(f)) {} ~RepeatedTimer() { { @@ -28,7 +35,6 @@ public: run_ = false; } wait_.notify_all(); - thread_.join(); } private: @@ -50,9 +56,9 @@ private: std::mutex mutex_; std::condition_variable wait_; bool run_ = true; - std::thread thread_; + ScopedThread thread_; }; } // namespace kotlin -#endif // RUNTIME_REPEATED_TIMER_H +#endif // !KONAN_NO_THREADS diff --git a/kotlin-native/runtime/src/main/cpp/ScopedThread.cpp b/kotlin-native/runtime/src/main/cpp/ScopedThread.cpp new file mode 100644 index 00000000000..f0fe0baa622 --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/ScopedThread.cpp @@ -0,0 +1,32 @@ +/* + * Copyright 2010-2022 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 KONAN_NO_THREADS + +#include "ScopedThread.hpp" + +#include +#include +#include + +#include "Logging.hpp" + +using namespace kotlin; + +void internal::setCurrentThreadName(std::string_view name) noexcept { +#if KONAN_MACOSX || KONAN_IOS || KONAN_WATCHOS || KONAN_TVOS + static_assert(std::is_invocable_r_v, "Invalid pthread_setname_np signature"); + pthread_setname_np(name.data()); +#else + static_assert(std::is_invocable_r_v, "Invalid pthread_setname_np signature"); + // TODO: On Linux the maximum thread name is 16 characters. Handle automatically? + int result = pthread_setname_np(pthread_self(), name.data()); + if (result != 0) { + RuntimeLogWarning({"rt"}, "Failed to set thread name: %s", std::strerror(result)); + } +#endif +} + +#endif // !KONAN_NO_THREADS diff --git a/kotlin-native/runtime/src/main/cpp/ScopedThread.hpp b/kotlin-native/runtime/src/main/cpp/ScopedThread.hpp new file mode 100644 index 00000000000..4d36d497962 --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/ScopedThread.hpp @@ -0,0 +1,101 @@ +/* + * Copyright 2010-2022 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. + */ + +#pragma once + +#ifndef KONAN_NO_THREADS + +#include +#include +#include +#include + +#include "Types.h" +#include "Utils.hpp" + +namespace kotlin { +namespace internal { + +void setCurrentThreadName(std::string_view name) noexcept; + +} // namespace internal + +// This is a combination of `std::jthread` and http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2019r0.pdf +// Missing stop token handling from `std::jthread`, and only a name attribute is supported from the paper. +class ScopedThread : private MoveOnly { +public: + using id = std::thread::id; + using native_handle_type = std::thread::native_handle_type; + + // There's no guarantee when and if these attributes will be applied. Use them as hints. + class attributes { + public: + attributes() noexcept = default; + attributes(const attributes&) noexcept = default; + attributes(attributes&&) noexcept = default; + attributes& operator=(const attributes&) noexcept = default; + attributes& operator=(attributes&&) noexcept = default; + ~attributes() = default; + + attributes& name(std::string_view name) noexcept { + name_ = name; + return *this; + } + + private: + friend class ScopedThread; + std::optional name_; + }; + + ScopedThread() noexcept = default; + + template < + typename F, + typename... Args, + typename = std::enable_if_t>, attributes>>> + explicit ScopedThread(F&& f, Args&&... args) : ScopedThread(attributes(), std::forward(f), std::forward(args)...) {} + + template + explicit ScopedThread(const attributes& attr, F&& f, Args&&... args) : + thread_(&ScopedThread::Run, attr, std::forward(f), std::forward(args)...) {} + + ScopedThread(ScopedThread&& rhs) noexcept = default; + ScopedThread& operator=(ScopedThread&& rhs) noexcept = default; + + ~ScopedThread() { + if (thread_.joinable()) { + thread_.join(); + } + } + + void swap(ScopedThread& rhs) noexcept { thread_.swap(rhs.thread_); } + + [[nodiscard]] static unsigned int hardware_concurrency() noexcept { return std::thread::hardware_concurrency(); } + + [[nodiscard]] bool joinable() const noexcept { return thread_.joinable(); } + + [[nodiscard]] id get_id() const noexcept { return thread_.get_id(); } + + [[nodiscard]] native_handle_type native_handle() { return thread_.native_handle(); } + + void join() { thread_.join(); } + + void detach() { thread_.detach(); } + +private: + template + static std::invoke_result_t Run(attributes attr, F&& f, Args&&... args) { + if (attr.name_) { + internal::setCurrentThreadName(*attr.name_); + } + return std::invoke(std::forward(f), std::forward(args)...); + } + + std::thread thread_; +}; + +} // namespace kotlin + +#endif // !KONAN_NO_THREADS diff --git a/kotlin-native/runtime/src/main/cpp/ScopedThreadTest.cpp b/kotlin-native/runtime/src/main/cpp/ScopedThreadTest.cpp new file mode 100644 index 00000000000..7d8332b12b9 --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/ScopedThreadTest.cpp @@ -0,0 +1,108 @@ +/* + * Copyright 2010-2022 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 "ScopedThread.hpp" + +#include +#include +#include +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "Format.h" +#include "KAssert.h" + +using namespace kotlin; + +namespace { + +template +std::string threadName(pthread_t thread) { + static_assert( + std::is_invocable_r_v, "Invalid pthread_getname_np signature"); + std::array name; + int result = pthread_getname_np(thread, name.data(), name.size()); + RuntimeAssert(result == 0, "failed to get thread name: %s\n", std::strerror(result)); + // Make sure name is null-terminated. + name[name.size() - 1] = '\0'; + return std::string(name.data()); +} + +__attribute__((format(printf, 1, 2))) std::string format(const char* format, ...) { + std::array buffer; + std::va_list args; + va_start(args, format); + VFormatToSpan(buffer, format, args); + va_end(args); + // `buffer` is guaranteed to be 0-terminated. + return std::string(buffer.data()); +} + +} // namespace + +TEST(ScopedThreadTest, Default) { + // Do not check name by default, since the default may be set by the system. + ScopedThread thread([] {}); +} + +TEST(ScopedThreadTest, ThreadName) { + ScopedThread thread(ScopedThread::attributes().name("some thread"), [] { EXPECT_THAT(threadName(pthread_self()), "some thread"); }); +} + +TEST(ScopedThreadTest, DynamicThreadName) { + ScopedThread thread( + ScopedThread::attributes().name(format("thread %d", 42)), [] { EXPECT_THAT(threadName(pthread_self()), "thread 42"); }); +} + +TEST(ScopedThreadTest, EmptyThreadName) { + ScopedThread thread(ScopedThread::attributes().name(""), [] { EXPECT_THAT(threadName(pthread_self()), ""); }); +} + +TEST(ScopedThreadTest, JoinsInDestructor) { + std::atomic exited = false; + { + ScopedThread([&exited] { + // Give a chance for the outer scope to go on. + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + exited = true; + }); + } + EXPECT_THAT(exited.load(), true); +} + +TEST(ScopedThreadTest, ManualJoin) { + std::atomic exited = false; + ScopedThread thread([&exited] { + // Give a chance for the outer scope to go on. + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + exited = true; + }); + EXPECT_THAT(thread.joinable(), true); + thread.join(); + EXPECT_THAT(thread.joinable(), false); + EXPECT_THAT(exited.load(), true); +} + +TEST(ScopedThreadTest, Detach) { + std::atomic exited = false; + { + ScopedThread thread([&exited] { + // Give a chance for the outer scope to go on. + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + exited = true; + }); + EXPECT_THAT(thread.joinable(), true); + thread.detach(); + EXPECT_THAT(thread.joinable(), false); + } + EXPECT_THAT(exited.load(), false); + // Wait for the thread to set `exited` before terminating test, otherwise the thread will write + // into freed `exited`. + while (!exited.load()) { + } +} diff --git a/kotlin-native/runtime/src/main/cpp/SingleLockListTest.cpp b/kotlin-native/runtime/src/main/cpp/SingleLockListTest.cpp index 77c28c236c9..2b2f691d303 100644 --- a/kotlin-native/runtime/src/main/cpp/SingleLockListTest.cpp +++ b/kotlin-native/runtime/src/main/cpp/SingleLockListTest.cpp @@ -7,11 +7,11 @@ #include #include -#include #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "ScopedThread.hpp" #include "TestSupport.hpp" #include "Types.h" @@ -111,7 +111,7 @@ TEST(SingleLockListTest, ConcurrentEmplace) { constexpr int kThreadCount = kDefaultThreadCount; std::atomic canStart(false); std::atomic readyCount(0); - KStdVector threads; + KStdVector threads; KStdVector expected; for (int i = 0; i < kThreadCount; ++i) { expected.push_back(i); @@ -126,9 +126,7 @@ TEST(SingleLockListTest, ConcurrentEmplace) { while (readyCount < kThreadCount) { } canStart = true; - for (auto& t : threads) { - t.join(); - } + threads.clear(); KStdVector actual; for (int element : list.LockForIter()) { @@ -148,7 +146,7 @@ TEST(SingleLockListTest, ConcurrentErase) { std::atomic canStart(false); std::atomic readyCount(0); - KStdVector threads; + KStdVector threads; for (auto* item : items) { threads.emplace_back([item, &list, &canStart, &readyCount]() { ++readyCount; @@ -161,9 +159,7 @@ TEST(SingleLockListTest, ConcurrentErase) { while (readyCount < kThreadCount) { } canStart = true; - for (auto& t : threads) { - t.join(); - } + threads.clear(); KStdVector actual; for (int element : list.LockForIter()) { @@ -188,7 +184,7 @@ TEST(SingleLockListTest, IterWhileConcurrentEmplace) { std::atomic canStart(false); std::atomic startedCount(0); - KStdVector threads; + KStdVector threads; for (int i = 0; i < kThreadCount; ++i) { int j = i + kStartCount; expectedAfter.push_back(j); @@ -212,9 +208,7 @@ TEST(SingleLockListTest, IterWhileConcurrentEmplace) { } } - for (auto& t : threads) { - t.join(); - } + threads.clear(); EXPECT_THAT(actualBefore, testing::ElementsAreArray(expectedBefore)); @@ -239,7 +233,7 @@ TEST(SingleLockListTest, IterWhileConcurrentErase) { std::atomic canStart(false); std::atomic startedCount(0); - KStdVector threads; + KStdVector threads; for (auto* item : items) { threads.emplace_back([item, &list, &canStart, &startedCount]() { while (!canStart) { @@ -261,9 +255,7 @@ TEST(SingleLockListTest, IterWhileConcurrentErase) { } } - for (auto& t : threads) { - t.join(); - } + threads.clear(); EXPECT_THAT(actualBefore, testing::ElementsAreArray(expectedBefore)); @@ -279,7 +271,7 @@ TEST(SingleLockListTest, LockAndEmplace) { SingleLockList list; constexpr int kThreadCount = kDefaultThreadCount; - KStdVector threads; + KStdVector threads; KStdVector actualLocked; KStdVector actualUnlocked; KStdVector expectedUnlocked; @@ -306,9 +298,7 @@ TEST(SingleLockListTest, LockAndEmplace) { } } - for (auto& thread : threads) { - thread.join(); - } + threads.clear(); for (int element : list.LockForIter()) { actualUnlocked.push_back(element); } @@ -322,7 +312,7 @@ TEST(SingleLockListTest, LockAndErase) { KStdVector::Node*> items; KStdVector expectedLocked; - KStdVector threads; + KStdVector threads; KStdVector actualLocked; KStdVector actualUnlocked; std::atomic startedCount(0); @@ -350,9 +340,7 @@ TEST(SingleLockListTest, LockAndErase) { } } - for (auto& thread : threads) { - thread.join(); - } + threads.clear(); for (int element : list.LockForIter()) { actualUnlocked.push_back(element); } diff --git a/kotlin-native/runtime/src/main/cpp/SingleThreadExecutor.hpp b/kotlin-native/runtime/src/main/cpp/SingleThreadExecutor.hpp index cb88a0854ee..3057fb70df0 100644 --- a/kotlin-native/runtime/src/main/cpp/SingleThreadExecutor.hpp +++ b/kotlin-native/runtime/src/main/cpp/SingleThreadExecutor.hpp @@ -10,8 +10,8 @@ #include #include #include -#include +#include "ScopedThread.hpp" #include "Types.h" #include "Utils.hpp" @@ -54,7 +54,7 @@ public: } // Id of the worker thread. - std::thread::id threadId() const noexcept { return thread_.get_id(); } + ScopedThread::id threadId() const noexcept { return thread_.get_id(); } // Schedule task execution on the worker thread. The returned future is resolved when the task has completed. // If `this` is destroyed before the task manages to complete, the returned future will fail with exception upon `.get()`. @@ -104,7 +104,7 @@ private: KStdDeque> queue_; bool shutdownRequested_ = false; - std::thread thread_; + ScopedThread thread_; }; } // namespace kotlin diff --git a/kotlin-native/runtime/src/main/cpp/SingleThreadExecutorTest.cpp b/kotlin-native/runtime/src/main/cpp/SingleThreadExecutorTest.cpp index c8c2af2e9c7..590c48e3ccf 100644 --- a/kotlin-native/runtime/src/main/cpp/SingleThreadExecutorTest.cpp +++ b/kotlin-native/runtime/src/main/cpp/SingleThreadExecutorTest.cpp @@ -55,7 +55,7 @@ testing::MockFunction* PinnedContext::dtorMock = nullptr; TEST(SingleThreadExecutorTest, ContextThreadBound) { PinnedContext::ScopedMocks mocks; PinnedContext* createdContext = nullptr; - std::thread::id createdThread; + ScopedThread::id createdThread; EXPECT_CALL(mocks.ctorMock, Call(_)).WillOnce([&](PinnedContext& context) { createdContext = &context; createdThread = std::this_thread::get_id(); @@ -172,7 +172,7 @@ TEST(SingleThreadExecutorTest, ExecuteFromManyThreads) { std::atomic_bool canStart = false; KStdVector expected; - KStdVector threads; + KStdVector threads; for (int i = 0; i < kDefaultThreadCount; ++i) { expected.push_back(i); threads.emplace_back([&, i] { @@ -184,9 +184,7 @@ TEST(SingleThreadExecutorTest, ExecuteFromManyThreads) { canStart = true; - for (auto& thread : threads) { - thread.join(); - } + threads.clear(); EXPECT_THAT(executor.context().result, testing::UnorderedElementsAreArray(expected)); } diff --git a/kotlin-native/runtime/src/main/cpp/TestSupport.hpp b/kotlin-native/runtime/src/main/cpp/TestSupport.hpp index 56509392f6e..72975b03353 100644 --- a/kotlin-native/runtime/src/main/cpp/TestSupport.hpp +++ b/kotlin-native/runtime/src/main/cpp/TestSupport.hpp @@ -6,10 +6,10 @@ #pragma once #include -#include #include "Memory.h" #include "Runtime.h" +#include "ScopedThread.hpp" namespace kotlin { @@ -48,10 +48,10 @@ private: // Runs the given function in a separate thread with minimally initialized runtime. inline void RunInNewThread(std::function f) { - std::thread([&f]() { + ScopedThread([&f]() { ScopedMemoryInit init; f(init.memoryState()); - }).join(); + }); } // Runs the given function in a separate thread with minimally initialized runtime. diff --git a/kotlin-native/runtime/src/mm/cpp/ExtraObjectDataTest.cpp b/kotlin-native/runtime/src/mm/cpp/ExtraObjectDataTest.cpp index 6b9af6b8049..8f51bdf871f 100644 --- a/kotlin-native/runtime/src/mm/cpp/ExtraObjectDataTest.cpp +++ b/kotlin-native/runtime/src/mm/cpp/ExtraObjectDataTest.cpp @@ -6,12 +6,12 @@ #include "ExtraObjectData.hpp" #include -#include #include "gmock/gmock.h" #include "gtest/gtest.h" #include "ObjectTestSupport.hpp" +#include "ScopedThread.hpp" #include "TestSupport.hpp" using namespace kotlin; @@ -68,7 +68,7 @@ TEST_F(ExtraObjectDataTest, ConcurrentInstall) { std::atomic canStart(false); std::atomic readyCount(0); - std::vector threads; + std::vector threads; std::vector actual(kThreadCount, nullptr); for (int i = 0; i < kThreadCount; ++i) { @@ -86,9 +86,7 @@ TEST_F(ExtraObjectDataTest, ConcurrentInstall) { while (readyCount < kThreadCount) { } canStart = true; - for (auto& t : threads) { - t.join(); - } + threads.clear(); std::vector expected(kThreadCount, actual[0]); diff --git a/kotlin-native/runtime/src/mm/cpp/InitializationSchemeTest.cpp b/kotlin-native/runtime/src/mm/cpp/InitializationSchemeTest.cpp index 5878c3800e4..d149726dfab 100644 --- a/kotlin-native/runtime/src/mm/cpp/InitializationSchemeTest.cpp +++ b/kotlin-native/runtime/src/mm/cpp/InitializationSchemeTest.cpp @@ -6,12 +6,12 @@ #include "InitializationScheme.hpp" #include -#include #include "gmock/gmock.h" #include "gtest/gtest.h" #include "ObjectTestSupport.hpp" +#include "ScopedThread.hpp" #include "TestSupport.hpp" #include "ThreadData.hpp" #include "Types.h" @@ -200,7 +200,7 @@ TEST_F(InitSingletonTest, InitSingletonConcurrent) { constexpr size_t kThreadCount = kDefaultThreadCount; std::atomic canStart(false); std::atomic readyCount(0); - KStdVector threads; + KStdVector threads; ObjHeader* location = nullptr; KStdVector stackLocations(kThreadCount, nullptr); KStdVector actual(kThreadCount, nullptr); @@ -222,9 +222,7 @@ TEST_F(InitSingletonTest, InitSingletonConcurrent) { // Constructor is called exactly once. EXPECT_CALL(constructor(), Call(_)); canStart = true; - for (auto& t : threads) { - t.join(); - } + threads.clear(); testing::Mock::VerifyAndClearExpectations(&constructor()); EXPECT_THAT(location, testing::Not(testing::Truly(isNullOrMarker))); @@ -236,7 +234,7 @@ TEST_F(InitSingletonTest, InitSingletonConcurrentFailing) { constexpr size_t kThreadCount = kDefaultThreadCount; std::atomic canStart(false); std::atomic readyCount(0); - KStdVector threads; + KStdVector threads; constexpr int kException = 42; ObjHeader* location = nullptr; KStdVector stackLocations(kThreadCount, nullptr); @@ -263,9 +261,7 @@ TEST_F(InitSingletonTest, InitSingletonConcurrentFailing) { // Constructor is called exactly `kThreadCount` times. EXPECT_CALL(constructor(), Call(_)).Times(kThreadCount).WillRepeatedly([]() { throw kException; }); canStart = true; - for (auto& t : threads) { - t.join(); - } + threads.clear(); testing::Mock::VerifyAndClearExpectations(&constructor()); EXPECT_THAT(location, nullptr); diff --git a/kotlin-native/runtime/src/mm/cpp/MutexTest.cpp b/kotlin-native/runtime/src/mm/cpp/MutexTest.cpp index fd1d2d75f12..9f10189f414 100644 --- a/kotlin-native/runtime/src/mm/cpp/MutexTest.cpp +++ b/kotlin-native/runtime/src/mm/cpp/MutexTest.cpp @@ -5,9 +5,9 @@ #include "Mutex.hpp" -#include - #include "gtest/gtest.h" + +#include "ScopedThread.hpp" #include "TestSupport.hpp" #include "ThreadState.hpp" @@ -42,14 +42,14 @@ TYPED_TEST(MutexTestNewMM, SmokeAttachedThread) { ThreadState initialThreadState = TypeParam::initialState; LockUnderTest lock; - std::thread secondThread; + ScopedThread secondThread; std::atomic started = false; std::atomic protectedCounter = 0; mm::ThreadData* secondThreadData = nullptr; { std::unique_lock guard1(lock); - secondThread = std::thread([&lock, &started, &protectedCounter, &secondThreadData, &initialThreadState]() { + secondThread = ScopedThread([&lock, &started, &protectedCounter, &secondThreadData, &initialThreadState]() { ScopedMemoryInit init; SwitchThreadState(init.memoryState(), initialThreadState, /* reentrant = */ true); diff --git a/kotlin-native/runtime/src/mm/cpp/ObjectFactoryTest.cpp b/kotlin-native/runtime/src/mm/cpp/ObjectFactoryTest.cpp index 4edb22b5af4..7d16fde8895 100644 --- a/kotlin-native/runtime/src/mm/cpp/ObjectFactoryTest.cpp +++ b/kotlin-native/runtime/src/mm/cpp/ObjectFactoryTest.cpp @@ -6,7 +6,6 @@ #include "ObjectFactory.hpp" #include -#include #include #include "gmock/gmock.h" @@ -16,6 +15,7 @@ #include "FinalizerHooksTestSupport.hpp" #include "ObjectOps.hpp" #include "ObjectTestSupport.hpp" +#include "ScopedThread.hpp" #include "TestSupport.hpp" #include "Types.h" @@ -630,7 +630,7 @@ TEST(ObjectFactoryStorageTest, ConcurrentPublish) { constexpr int kThreadCount = kDefaultThreadCount; std::atomic canStart(false); std::atomic readyCount(0); - KStdVector threads; + KStdVector threads; KStdVector expected; for (int i = 0; i < kThreadCount; ++i) { @@ -648,9 +648,7 @@ TEST(ObjectFactoryStorageTest, ConcurrentPublish) { while (readyCount < kThreadCount) { } canStart = true; - for (auto& t : threads) { - t.join(); - } + threads.clear(); auto actual = Collect(storage); @@ -676,7 +674,7 @@ TEST(ObjectFactoryStorageTest, IterWhileConcurrentPublish) { std::atomic canStart(false); std::atomic readyCount(0); std::atomic startedCount(0); - KStdVector threads; + KStdVector threads; for (int i = 0; i < kThreadCount; ++i) { int j = i + kStartCount; expectedAfter.push_back(j); @@ -706,9 +704,7 @@ TEST(ObjectFactoryStorageTest, IterWhileConcurrentPublish) { } } - for (auto& t : threads) { - t.join(); - } + threads.clear(); EXPECT_THAT(actualBefore, testing::ElementsAreArray(expectedBefore)); @@ -736,7 +732,7 @@ TEST(ObjectFactoryStorageTest, EraseWhileConcurrentPublish) { std::atomic canStart(false); std::atomic readyCount(0); std::atomic startedCount(0); - KStdVector threads; + KStdVector threads; for (int i = 0; i < kThreadCount; ++i) { int j = i + kStartCount; expectedAfter.push_back(j); @@ -768,9 +764,7 @@ TEST(ObjectFactoryStorageTest, EraseWhileConcurrentPublish) { } } - for (auto& t : threads) { - t.join(); - } + threads.clear(); auto actual = Collect(storage); @@ -1110,7 +1104,7 @@ TEST(ObjectFactoryTest, ConcurrentPublish) { constexpr int kThreadCount = kDefaultThreadCount; std::atomic canStart(false); std::atomic readyCount(0); - KStdVector threads; + KStdVector threads; std::mutex expectedMutex; KStdVector expected; @@ -1135,9 +1129,7 @@ TEST(ObjectFactoryTest, ConcurrentPublish) { } testing::Mock::VerifyAndClearExpectations(&allocator); canStart = true; - for (auto& t : threads) { - t.join(); - } + threads.clear(); auto iter = objectFactory.LockForIter(); KStdVector actual; diff --git a/kotlin-native/runtime/src/mm/cpp/ThreadRegistryTest.cpp b/kotlin-native/runtime/src/mm/cpp/ThreadRegistryTest.cpp index 9d9a7da4867..03cf716b7f7 100644 --- a/kotlin-native/runtime/src/mm/cpp/ThreadRegistryTest.cpp +++ b/kotlin-native/runtime/src/mm/cpp/ThreadRegistryTest.cpp @@ -5,17 +5,15 @@ #include "ThreadRegistry.hpp" -#include -#include - #include "gtest/gtest.h" +#include "ScopedThread.hpp" #include "ThreadData.hpp" using namespace kotlin; TEST(ThreadRegistryTest, RegisterCurrentThread) { - std::thread t([]() { + ScopedThread([]() { class ScopedRegistration { public: ScopedRegistration() : node(mm::ThreadRegistry::Instance().RegisterCurrentThread()) {} @@ -27,5 +25,4 @@ TEST(ThreadRegistryTest, RegisterCurrentThread) { EXPECT_EQ(konan::currentThreadId(), threadData->threadId()); EXPECT_EQ(threadData, mm::ThreadRegistry::Instance().CurrentThreadData()); }); - t.join(); } diff --git a/kotlin-native/runtime/src/mm/cpp/ThreadStateTest.cpp b/kotlin-native/runtime/src/mm/cpp/ThreadStateTest.cpp index ab316a0b8c5..9ae568b2df2 100644 --- a/kotlin-native/runtime/src/mm/cpp/ThreadStateTest.cpp +++ b/kotlin-native/runtime/src/mm/cpp/ThreadStateTest.cpp @@ -10,6 +10,7 @@ #include "Memory.h" #include "MemoryPrivate.hpp" +#include "ScopedThread.hpp" #include "TestSupport.hpp" #include "ThreadData.hpp" #include "ThreadState.hpp" @@ -110,7 +111,7 @@ TEST_F(ThreadStateTest, StateGuardForCurrentThread) { } TEST_F(ThreadStateTest, CalledFromNativeGuard_DetachedThread) { - std::thread t([] { + ScopedThread([] { ASSERT_FALSE(mm::IsCurrentThreadRegistered()); { CalledFromNativeGuard guard; @@ -120,11 +121,10 @@ TEST_F(ThreadStateTest, CalledFromNativeGuard_DetachedThread) { EXPECT_TRUE(mm::IsCurrentThreadRegistered()); EXPECT_EQ(mm::GetMemoryState()->GetThreadData()->state(), ThreadState::kNative); }); - t.join(); } TEST_F(ThreadStateTest, CalledFromNativeGuard_AttachedThread) { - std::thread t([] { + ScopedThread([] { // CalledFromNativeGuard checks that runtime is fully initialized under the hood. Kotlin_initRuntimeIfNeeded(); @@ -136,11 +136,10 @@ TEST_F(ThreadStateTest, CalledFromNativeGuard_AttachedThread) { } EXPECT_EQ(threadData->state(), ThreadState::kNative); }); - t.join(); } TEST_F(ThreadStateTest, NativeOrUnregisteredThreadGuard_DetachedThread) { - std::thread t([] { + ScopedThread([] { { ASSERT_FALSE(mm::IsCurrentThreadRegistered()); NativeOrUnregisteredThreadGuard guard; @@ -148,7 +147,6 @@ TEST_F(ThreadStateTest, NativeOrUnregisteredThreadGuard_DetachedThread) { } EXPECT_FALSE(mm::IsCurrentThreadRegistered()); }); - t.join(); } TEST_F(ThreadStateTest, NativeOrUnregisteredThreadGuard_AttachedThread) { @@ -341,7 +339,7 @@ TEST(ThreadStateDeathTest, ReentrantStateSwitch_NativeOrUnregisteredThreadGuard) } TEST(ThreadStateDeathTest, ReentrantStateSwitch_CalledFromNativeGuard) { - std::thread t([]() { + ScopedThread([]() { // CalledFromNativeGuard checks that runtime is fully initialized under the hood. Kotlin_initRuntimeIfNeeded(); auto* threadData = mm::GetMemoryState()->GetThreadData(); @@ -367,7 +365,6 @@ TEST(ThreadStateDeathTest, ReentrantStateSwitch_CalledFromNativeGuard) { } EXPECT_EQ(threadData->state(), ThreadState::kNative); }); - t.join(); } @@ -406,4 +403,4 @@ TEST(ThreadStateDeathTest, GuardForDetachedThread) { EXPECT_DEATH({ ThreadStateGuard guard(ThreadState::kNative, false); }, expectedError); EXPECT_DEATH({ ThreadStateGuard guard(ThreadState::kRunnable, true); }, expectedError); EXPECT_DEATH({ ThreadStateGuard guard(ThreadState::kNative, true); }, expectedError); -} \ No newline at end of file +} diff --git a/kotlin-native/runtime/src/mm/cpp/ThreadSuspensionTest.cpp b/kotlin-native/runtime/src/mm/cpp/ThreadSuspensionTest.cpp index d9c836d6e21..8a165c6e829 100644 --- a/kotlin-native/runtime/src/mm/cpp/ThreadSuspensionTest.cpp +++ b/kotlin-native/runtime/src/mm/cpp/ThreadSuspensionTest.cpp @@ -6,6 +6,7 @@ #include "MemoryPrivate.hpp" #include "Runtime.h" #include "RuntimePrivate.hpp" +#include "ScopedThread.hpp" #include "ThreadSuspension.hpp" #include "ThreadState.hpp" @@ -13,7 +14,6 @@ #include #include -#include #include #include @@ -77,15 +77,12 @@ 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 threads; + KStdVector threads; std::array, kThreadCount> ready{false}; std::atomic canStart{false}; std::atomic shouldStop{false}; @@ -270,4 +267,4 @@ TEST_F(ThreadSuspensionTest, FileInitializationWithSuspend) { for (auto& t : threads) { t.join(); } -} \ No newline at end of file +}