Implement set_thread_name and get_thread_name for debugging purposes
This commit is contained in:
parent
f201addbaf
commit
29f7f06ca9
@ -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
|
||||
|
@ -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
|
||||
|
16
src/cpp-utils/thread/debugging.h
Normal file
16
src/cpp-utils/thread/debugging.h
Normal 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
|
55
src/cpp-utils/thread/debugging_nonwindows.cpp
Normal file
55
src/cpp-utils/thread/debugging_nonwindows.cpp
Normal 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
|
111
src/cpp-utils/thread/debugging_windows.cpp
Normal file
111
src/cpp-utils/thread/debugging_windows.cpp
Normal 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
|
@ -54,6 +54,7 @@ set(SOURCES
|
||||
system/MemoryTest.cpp
|
||||
system/HomedirTest.cpp
|
||||
system/EnvTest.cpp
|
||||
thread/debugging_test.cpp
|
||||
value_type/ValueTypeTest.cpp
|
||||
)
|
||||
|
||||
|
62
test/cpp-utils/thread/debugging_test.cpp
Normal file
62
test/cpp-utils/thread/debugging_test.cpp
Normal 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();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user