[K/N] Add ScopedThread

* thread that by default joins in the destructor (like C++20 jthread)
* can be given a name

Merge-request: KT-MR-5619
Merged-by: Alexander Shabalin <Alexander.Shabalin@jetbrains.com>
This commit is contained in:
Alexander Shabalin
2022-02-03 15:38:07 +00:00
committed by Space
parent 9826172720
commit 434213f416
23 changed files with 345 additions and 133 deletions
@@ -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 <array>
#include <atomic>
#include <cstring>
#include <pthread.h>
#include <type_traits>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "Format.h"
#include "KAssert.h"
using namespace kotlin;
namespace {
template <size_t NAME_SIZE = 100>
std::string threadName(pthread_t thread) {
static_assert(
std::is_invocable_r_v<int, decltype(pthread_getname_np), pthread_t, char*, size_t>, "Invalid pthread_getname_np signature");
std::array<char, NAME_SIZE> 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<char, 20> 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<bool> 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<bool> 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<bool> 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()) {
}
}