From 99225ef050f4b3795524f641c2f98ade833a3686 Mon Sep 17 00:00:00 2001 From: David Capello Date: Tue, 29 Aug 2023 11:58:27 -0300 Subject: [PATCH] Add functions to set/get the active thread name --- base/thread.cpp | 75 +++++++++++++++++++++++++++++++++++++++++++ base/thread.h | 8 +++++ base/thread_tests.cpp | 50 +++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+) create mode 100644 base/thread_tests.cpp diff --git a/base/thread.cpp b/base/thread.cpp index 1779ab1ea..093f7966f 100644 --- a/base/thread.cpp +++ b/base/thread.cpp @@ -12,13 +12,46 @@ #include "base/thread.h" #if LAF_WINDOWS + #include "base/dll.h" + #include "base/string.h" + #include #include + #include #else #include // Use pthread library in Unix-like systems #include #include + #include +#endif + +#if LAF_WINDOWS +namespace { + +class KernelBaseApi { +public: + using SetThreadDescription_Func = HRESULT(WINAPI*)(HANDLE, PCWSTR); + using GetThreadDescription_Func = HRESULT(WINAPI*)(HANDLE, PWSTR*); + + SetThreadDescription_Func SetThreadDescription = nullptr; + GetThreadDescription_Func GetThreadDescription = nullptr; + + KernelBaseApi() { + m_dll = base::load_dll("KernelBase.dll"); + if (m_dll) { + SetThreadDescription = base::get_dll_proc(m_dll, "SetThreadDescription"); + GetThreadDescription = base::get_dll_proc(m_dll, "GetThreadDescription"); + } + } + +private: + base::dll m_dll; +}; + +KernelBaseApi kernelBaseApi; + +} // anonymous namespace #endif namespace base { @@ -56,4 +89,46 @@ void this_thread::sleep_for(double seconds) #endif } +void this_thread::set_name(const std::string& name) +{ +#if LAF_WINDOWS + if (kernelBaseApi.SetThreadDescription) + kernelBaseApi.SetThreadDescription(GetCurrentThread(), + base::from_utf8(name).c_str()); +#elif LAF_MACOS + // macOS has a non-standard pthread_setname_np() impl + pthread_setname_np(name.c_str()); +#else + int res = pthread_setname_np(pthread_self(), name.c_str()); + if (res != 0 && name.size() > 15) { + // Try with a shorter string (no more than 16 chars including the + // null char, as the spec says). + pthread_setname_np(pthread_self(), name.substr(0, 15).c_str()); + } +#endif +} + +std::string this_thread::get_name() +{ +#if LAF_WINDOWS + if (kernelBaseApi.GetThreadDescription) { + PWSTR desc = nullptr; + HRESULT hr = kernelBaseApi.GetThreadDescription(GetCurrentThread(), &desc); + if (SUCCEEDED(hr) && desc) { + std::string result(base::to_utf8(desc)); + LocalFree(desc); + return result; + } + } +#else + char name[65]; + int result = pthread_getname_np(pthread_self(), name, sizeof(name)-1); + if (result == 0) { // Returns 0 if it was successful + // pthread_getname_np() returns a null terminated name. + return std::string(name); + } +#endif + return std::string(); +} + } // namespace base diff --git a/base/thread.h b/base/thread.h index 91bfc5980..613999575 100644 --- a/base/thread.h +++ b/base/thread.h @@ -9,6 +9,8 @@ #define BASE_THREAD_H_INCLUDED #pragma once +#include + namespace base { namespace this_thread { @@ -17,6 +19,12 @@ void yield(); // TODO replace with std::this_thread::sleep_for(std::chrono::seconds(...)) or similar void sleep_for(double seconds); +// Associates a name/description to the current thread. Useful for +// debugging purposes. E.g. When we receive a crash dump from Sentry +// we can identify a thread by its name. +void set_name(const std::string& name); +std::string get_name(); + } // this_thread } // base diff --git a/base/thread_tests.cpp b/base/thread_tests.cpp new file mode 100644 index 000000000..60f30e91a --- /dev/null +++ b/base/thread_tests.cpp @@ -0,0 +1,50 @@ +// LAF Base Library +// Copyright (c) 2023 Igara Studio S.A. +// +// This file is released under the terms of the MIT license. +// Read LICENSE.txt for more information. + +#include + +#include "base/thread.h" + +using namespace base; + +TEST(Thread, SetGetName) +{ + // Empty string by default + EXPECT_EQ("", this_thread::get_name()); + + this_thread::set_name("main"); + EXPECT_EQ("main", this_thread::get_name()); + + this_thread::set_name("testing"); + EXPECT_EQ("testing", this_thread::get_name()); +} + +TEST(Thread, NameLimits) +{ + const char* fullName = "123456789012345678901234567890" + "123456789012345678901234567890" + "123456789012345678901234567890"; + + this_thread::set_name(fullName); + +#if LAF_WINDOWS + EXPECT_EQ(fullName, this_thread::get_name()); +#elif LAF_APPLE + // Limited to 64 chars + EXPECT_EQ("123456789012345678901234567890" + "123456789012345678901234567890" + "1234", this_thread::get_name()); +#else + // Limited to 16 chars (including the null char) + EXPECT_EQ("123456789012345", this_thread::get_name()); +#endif +} + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}