Implement set_thread_name and get_thread_name for debugging purposes

This commit is contained in:
Sebastian Messmer 2019-01-20 03:21:20 -08:00
parent f201addbaf
commit 29f7f06ca9
7 changed files with 249 additions and 1 deletions

View File

@ -36,7 +36,8 @@ build_script:
- cmd: cmake .. -G "Ninja" -DBUILD_TESTING=on -DBOOST_ROOT="C:/Libraries/boost_1_65_1" -DDOKAN_PATH="C:/Program Files/Dokan/DokanLibrary-1.1.0"
- cmd: cmake --build . --config %CONFIGURATION%
- cmd: .\test\gitversion\gitversion-test.exe
- cmd: cd .\test\cpp-utils\ && .\cpp-utils-test.exe && cd ..\..
# cpp-utils-test disables ThreadDebuggingTest_ThreadName.*_thenIsCorrect because the appveyor image is too old to support the API needed for that
- cmd: cd .\test\cpp-utils\ && .\cpp-utils-test.exe --gtest_filter=-ThreadDebuggingTest_ThreadName.*_thenIsCorrect && cd ..\..
#- cmd: .\test\fspp\fspp-test.exe
- cmd: .\test\parallelaccessstore\parallelaccessstore-test.exe
- cmd: .\test\blockstore\blockstore-test.exe

View File

@ -24,6 +24,8 @@ set(SOURCES
io/pipestream.cpp
thread/LoopThread.cpp
thread/ThreadSystem.cpp
thread/debugging_nonwindows.cpp
thread/debugging_windows.cpp
random/Random.cpp
random/RandomGeneratorThread.cpp
random/OSRandomGenerator.cpp

View File

@ -0,0 +1,16 @@
#pragma once
#ifndef MESSMER_CPPUTILS_DEBUGGING_H
#define MESSMER_CPPUTILS_DEBUGGING_H
#include <string>
#include <thread>
namespace cpputils {
void set_thread_name(const char* name);
std::string get_thread_name();
std::string get_thread_name(std::thread* thread);
}
#endif

View File

@ -0,0 +1,55 @@
#if !defined(_MSC_VER)
#include "debugging.h"
#include <stdexcept>
#include <thread>
#include <pthread.h>
#include <cpp-utils/assert/assert.h>
namespace cpputils {
namespace {
constexpr size_t MAX_NAME_LEN = 16; // this length includes the terminating null character at the end
}
void set_thread_name(const char* name) {
std::string name_(name);
if (name_.size() > MAX_NAME_LEN - 1) {
name_.resize(MAX_NAME_LEN - 1);
}
#if defined(__APPLE__)
int result = pthread_setname_np(name_.c_str());
#else
int result = pthread_setname_np(pthread_self(), name_.c_str());
#endif
if (0 != result) {
throw std::runtime_error("Error setting thread name with pthread_setname_np. Code: " + std::to_string(result));
}
}
namespace {
std::string get_thread_name(pthread_t thread) {
char name[MAX_NAME_LEN];
int result = pthread_getname_np(thread, name, MAX_NAME_LEN);
if (0 != result) {
throw std::runtime_error("Error getting thread name with pthread_getname_np. Code: " + std::to_string(result));
}
// pthread_getname_np returns a null terminated string with maximum 16 bytes.
// but just to be safe against a buggy implementation, let's set the last byte to zero.
name[MAX_NAME_LEN - 1] = '\0';
return name;
}
}
std::string get_thread_name() {
return get_thread_name(pthread_self());
}
std::string get_thread_name(std::thread* thread) {
ASSERT(thread->joinable(), "Thread not running");
return get_thread_name(thread->native_handle());
}
}
#endif

View File

@ -0,0 +1,111 @@
#if defined(_MSC_VER)
#include <Windows.h>
#include "debugging.h"
#include <codecvt>
#include <cpp-utils/assert/assert.h>
using std::string;
using std::wstring;
using std::wstring_convert;
namespace cpputils {
namespace {
struct NameData final {
wchar_t *name = nullptr;
~NameData() {
if (nullptr != LocalFree(name)) {
throw std::runtime_error("Error releasing thread description memory. Error code: " + std::to_string(GetLastError()));
}
}
};
struct ModuleHandle final {
HMODULE module;
ModuleHandle(const char* dll) {
bool success = GetModuleHandleExA(0, dll, &module);
if (!success) {
throw std::runtime_error(string() + "Error loading dll: " + dll + ". Error code: " + std::to_string(GetLastError()));
}
}
~ModuleHandle() {
bool success = FreeLibrary(module);
if (!success) {
throw std::runtime_error("Error unloading dll. Error code: " + std::to_string(GetLastError()));
}
}
};
template<class Fn>
class APIFunction final {
private:
ModuleHandle module_;
Fn func_;
public:
APIFunction(const char* dll, const char* function)
: module_(dll), func_(reinterpret_cast<Fn>(GetProcAddress(module_.module, function))) {
}
bool valid() const {
return func_ != nullptr;
}
Fn func() const {
return func_;
}
};
std::string get_thread_name(HANDLE thread) {
// The GetThreadDescription API was brought in version 1607 of Windows 10.
typedef HRESULT(WINAPI* GetThreadDescriptionFn)(HANDLE hThread, PWSTR* ppszThreadDescription);
static APIFunction<GetThreadDescriptionFn> get_thread_description_func("Kernel32.dll", "GetThreadDescription");
if (get_thread_description_func.valid()) {
NameData name_data;
HRESULT status = get_thread_description_func.func()(thread, &name_data.name);
if (FAILED(status)) {
throw std::runtime_error("Error getting thread description. Error code: " + std::to_string(status));
}
return wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(name_data.name);
}
else {
// GetThreadDescription API is not available.
return "";
}
}
}
void set_thread_name(const char* name) {
// The GetThreadDescription API was brought in version 1607 of Windows 10.
typedef HRESULT(WINAPI* SetThreadDescriptionFn)(HANDLE hThread, PCWSTR lpThreadDescription);
static APIFunction<SetThreadDescriptionFn> set_thread_description_func("Kernel32.dll", "SetThreadDescription");
if (set_thread_description_func.valid()) {
wstring wname = wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(name);
HRESULT status = set_thread_description_func.func()(GetCurrentThread(), wname.c_str());
if (FAILED(status)) {
throw std::runtime_error("Error setting thread description. Error code: " + std::to_string(status));
}
}
else {
// intentionally empty. SetThreadDescription API is not available.
}
}
std::string get_thread_name() {
return get_thread_name(GetCurrentThread());
}
std::string get_thread_name(std::thread* thread) {
ASSERT(thread->joinable(), "Thread not running");
return get_thread_name(static_cast<HANDLE>(thread->native_handle()));
}
}
#endif

View File

@ -54,6 +54,7 @@ set(SOURCES
system/MemoryTest.cpp
system/HomedirTest.cpp
system/EnvTest.cpp
thread/debugging_test.cpp
value_type/ValueTypeTest.cpp
)

View File

@ -0,0 +1,62 @@
#include <cpp-utils/thread/debugging.h>
#include <cpp-utils/assert/assert.h>
#include <cpp-utils/lock/ConditionBarrier.h>
#include <gtest/gtest.h>
using namespace cpputils;
using std::string;
TEST(ThreadDebuggingTest_ThreadName, givenMainThread_whenSettingAndGetting_thenDoesntCrash) {
set_thread_name("my_thread_name");
get_thread_name();
}
TEST(ThreadDebuggingTest_ThreadName, givenChildThread_whenSettingAndGetting_thenDoesntCrash) {
ConditionBarrier nameIsChecked;
bool child_didnt_crash = false;
std::thread child([&] {
set_thread_name("my_thread_name");
get_thread_name();
child_didnt_crash = true;
nameIsChecked.wait();
});
get_thread_name(&child);
nameIsChecked.release(); // getting the name of a not-running thread would cause errors, so let's make sure we only exit after getting the name
child.join();
EXPECT_TRUE(child_didnt_crash);
}
TEST(ThreadDebuggingTest_ThreadName, givenMainThread_whenGettingFromInside_thenIsCorrect) {
set_thread_name("my_thread_name");
string name = get_thread_name();
EXPECT_EQ("my_thread_name", name);
}
TEST(ThreadDebuggingTest_ThreadName, givenChildThread_whenGettingFromInside_thenIsCorrect) {
std::thread child([] {
set_thread_name("my_thread_name");
string name = get_thread_name();
EXPECT_EQ("my_thread_name", name);
});
child.join();
}
TEST(ThreadDebuggingTest_ThreadName, givenChildThread_whenGettingFromOutside_thenIsCorrect) {
ConditionBarrier nameIsSet;
ConditionBarrier nameIsChecked;
std::thread child([&] {
set_thread_name("my_thread_name");
nameIsSet.release();
nameIsChecked.wait();
});
nameIsSet.wait();
string name = get_thread_name(&child);
EXPECT_EQ("my_thread_name", name);
nameIsChecked.release();
child.join();
}