From dcaa15af6bf17fa8448f0ca7bdde451c3e3d2f27 Mon Sep 17 00:00:00 2001 From: Aleksandar Fabijanic Date: Tue, 23 Dec 2025 07:40:18 -0600 Subject: [PATCH] Feature native thread info (#5135) * Add NativeThreadInfo + use in Message + test with PatternFormatter * Fix NativeThreadInfo for glibc without gettid * add new file to make * chore: formatting * fix(NativeThreadinfo): name and id #3333 * fix(NativeThread): add OS includes #3333 * fix(Message): use OS thread ID for non-POCO threads #3333 Fix Message initialization to correctly handle thread IDs for both POCO and non-POCO threads. When Thread::current() returns null (std::thread, main thread, etc.), use NativeThreadInfo to get the OS thread ID instead of leaving _tid as zero. Rename NativeThreadInfo::id() to NativeThreadInfo::osTid() to match the Thread::currentOsTid() naming convention and clarify that both return the same kernel-level thread identifier (not the POCO sequential ID). Additional changes: - Add comprehensive documentation to NativeThreadInfo explaining its purpose and when to use it vs Thread::current() - Fix LoggerTest warning by removing pessimizing std::move in return - Update all platform implementations (POSIX, WIN32, WINCE, VxWorks) This ensures the %I format specifier in PatternFormatter shows: - POCO thread ID (1, 2, 3...) for Poco::Thread instances - OS thread ID for non-POCO threads (std::thread, main thread, etc.) * fix(LoggerTest): compare thread ID against actual value #3333 testFormatThreadName was failing in CI because it expected the POCO thread ID to be 1, but in a full test run, many threads are created before this test (by ThreadingTestSuite, etc.), resulting in higher IDs. The test now compares against the actual thread's ID (thr.id()) instead of a hard-coded value, making it robust regardless of test order. * fix(NativeThreadInfo): windows compile #3333 * fix(NativeThreadInfo): mac compile fail; consolidate platform pthread functions selection #3333 * fix(NativeThreadInfo): use UTF8 #3333 * fix: remove NativeThreadInfo (not needed) #3333 * fix(Thread): proper UTF-8 handling and test robustness #3333 - Thread_WIN32: Use MultiByteToWideChar with CP_UTF8 in setCurrentNameImpl instead of byte-by-byte iterator copy. The old conversion was incorrect for non-ASCII characters (e.g., Japanese thread names). - LoggerTest: Add bounds check (parts.size() >= 5) before accessing vector elements to prevent undefined behavior if log message format is unexpected. * fix(Message): disable thread name on platforms that don't have it #3333 * fix(Thread): make getCurrentName/setCurrentName always available #3333 Move POCO_NO_THREADNAME guard inside inline implementations instead of around declarations. This ensures the API is always available on all platforms, returning empty string or no-op on platforms without thread name support (e.g., emscripten, AIX). Update Message.cpp to remove conditional compilation since the functions are now unconditionally available. * fix(Thread): Windows backward compatibility and code cleanup #3333 Thread_WIN32.cpp: - Use dynamic loading for SetThreadDescription/GetThreadDescription via GetProcAddress for compatibility with pre-Windows 10 1607 - setCurrentNameImpl tries modern API first, falls back to legacy exception method (0x406D1388) for older Windows/debuggers - Remove redundant processthreadsapi.h and stringapiset.h includes (already included via windows.h) LoggerTest.cpp: - Simplify loop condition (remove redundant npos check) - Fix code style (braces on new lines) --------- Co-authored-by: Olivier Smeesters --- Foundation/include/Poco/Thread.h | 29 ++++++ Foundation/include/Poco/Thread_POSIX.h | 4 + Foundation/include/Poco/Thread_WIN32.h | 2 + Foundation/src/Message.cpp | 8 ++ Foundation/src/Thread.cpp | 3 +- Foundation/src/Thread_POSIX.cpp | 126 +++++++++++++----------- Foundation/src/Thread_WIN32.cpp | 70 +++++++++++++ Foundation/testsuite/src/LoggerTest.cpp | 101 ++++++++++++++++++- Foundation/testsuite/src/LoggerTest.h | 6 +- cppignore.win | 5 + 10 files changed, 291 insertions(+), 63 deletions(-) diff --git a/Foundation/include/Poco/Thread.h b/Foundation/include/Poco/Thread.h index 8bf6078d4..9e088a164 100644 --- a/Foundation/include/Poco/Thread.h +++ b/Foundation/include/Poco/Thread.h @@ -245,6 +245,17 @@ public: /// Returns the operating system specific thread ID for the current thread. /// On error, or if the platform does not support this functionality, it returns zero. + static void setCurrentName(const std::string& name); + /// Sets the name of the current thread. + /// Support for this feature varies across platforms. + /// Any errors are silently ignored. + + static std::string getCurrentName(); + /// Returns the name of the current thread. + /// Support for this feature varies across platforms. + /// Returns an empty string if not supported, on error, + /// or if no name has been set for the thread. + bool setAffinity(int coreId); /// Sets the thread affinity to the coreID. /// Returns true if succesful. @@ -434,6 +445,24 @@ inline long Thread::currentOsTid() return currentOsTidImpl(); } +inline void Thread::setCurrentName(const std::string& name) +{ +#ifndef POCO_NO_THREADNAME + setCurrentNameImpl(name); +#else + (void)name; +#endif +} + +inline std::string Thread::getCurrentName() +{ +#ifndef POCO_NO_THREADNAME + return getCurrentNameImpl(); +#else + return std::string(); +#endif +} + inline bool Thread::setAffinity(int coreId) { return setAffinityImpl(coreId); diff --git a/Foundation/include/Poco/Thread_POSIX.h b/Foundation/include/Poco/Thread_POSIX.h index d7b7a3eaf..9650000f6 100644 --- a/Foundation/include/Poco/Thread_POSIX.h +++ b/Foundation/include/Poco/Thread_POSIX.h @@ -87,6 +87,10 @@ public: static ThreadImpl* currentImpl(); static TIDImpl currentTidImpl(); static long currentOsTidImpl(); +#ifndef POCO_NO_THREADNAME + static void setCurrentNameImpl(const std::string& name); + static std::string getCurrentNameImpl(); +#endif bool setAffinityImpl(int coreID); int getAffinityImpl() const; diff --git a/Foundation/include/Poco/Thread_WIN32.h b/Foundation/include/Poco/Thread_WIN32.h index 3aa34277f..c3b026ffe 100644 --- a/Foundation/include/Poco/Thread_WIN32.h +++ b/Foundation/include/Poco/Thread_WIN32.h @@ -79,6 +79,8 @@ public: static ThreadImpl* currentImpl(); static TIDImpl currentTidImpl(); static long currentOsTidImpl(); + static void setCurrentNameImpl(const std::string& name); + static std::string getCurrentNameImpl(); bool setAffinityImpl(int); int getAffinityImpl() const; diff --git a/Foundation/src/Message.cpp b/Foundation/src/Message.cpp index f6bd4610a..cb6cf98fd 100644 --- a/Foundation/src/Message.cpp +++ b/Foundation/src/Message.cpp @@ -170,6 +170,14 @@ void Message::init() _tid = pThread->id(); _thread = pThread->name(); } + else + { + // Not a POCO thread, use native OS thread ID and name. + // getCurrentName() returns empty string if no name has been set + // or not available on the platform. + _tid = Thread::currentOsTid(); + _thread = Thread::getCurrentName(); + } } diff --git a/Foundation/src/Thread.cpp b/Foundation/src/Thread.cpp index 400ff347e..e549387a2 100644 --- a/Foundation/src/Thread.cpp +++ b/Foundation/src/Thread.cpp @@ -16,7 +16,6 @@ #include "Poco/Mutex.h" #include "Poco/Exception.h" #include "Poco/ThreadLocal.h" -#include "Poco/AtomicCounter.h" #include @@ -232,7 +231,7 @@ std::string Thread::makeName() int Thread::uniqueId() { - static Poco::AtomicCounter counter; + static std::atomic counter; return ++counter; } diff --git a/Foundation/src/Thread_POSIX.cpp b/Foundation/src/Thread_POSIX.cpp index b8befb513..7c8fd0923 100644 --- a/Foundation/src/Thread_POSIX.cpp +++ b/Foundation/src/Thread_POSIX.cpp @@ -84,61 +84,6 @@ namespace #endif -namespace -{ - std::string truncName(const std::string& name, std::size_t nameSize = POCO_MAX_THREAD_NAME_LEN) - { - if (name.size() > nameSize) - return name.substr(0, nameSize).append("~"); - return name; - } - -#ifndef POCO_NO_THREADNAME - void setThreadName(const std::string& threadName) - /// Sets thread name. Support for this feature varies - /// on platforms. Any errors are ignored. - { -#if (POCO_OS == POCO_OS_FREE_BSD) - pthread_setname_np(pthread_self(), truncName(threadName).c_str()); -#elif (POCO_OS == POCO_OS_MAC_OS_X) - #ifdef __MAC_OS_X_VERSION_MIN_REQUIRED - #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 - pthread_setname_np(truncName(threadName).c_str()); // __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2) - #endif - #endif // __MAC_OS_X_VERSION_MIN_REQUIRED -#elif (POCO_OS == POCO_OS_QNX) - pthread_setname_np(pthread_self(), truncName(threadName, _NTO_THREAD_NAME_MAX).c_str()); -#else - prctl(PR_SET_NAME, truncName(threadName).c_str()); -#endif - } - - std::string getThreadName() - { - constexpr size_t nameSize = -#if (POCO_OS == POCO_OS_QNX) - _NTO_THREAD_NAME_MAX; -#else - POCO_MAX_THREAD_NAME_LEN; -#endif - char name[nameSize + 1]{'\0'}; -#if (POCO_OS == POCO_OS_FREE_BSD) - pthread_getname_np(pthread_self(), name, nameSize + 1); -#elif (POCO_OS == POCO_OS_MAC_OS_X) - #ifdef __MAC_OS_X_VERSION_MIN_REQUIRED - #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 - pthread_getname_np(pthread_self(), name, nameSize + 1); - #endif - #endif // __MAC_OS_X_VERSION_MIN_REQUIRED -#elif (POCO_OS == POCO_OS_QNX) - pthread_getname_np(pthread_self(), name, nameSize); -#else - prctl(PR_GET_NAME, name); -#endif - return name; - } -#endif -} namespace Poco { @@ -198,7 +143,7 @@ std::string ThreadImpl::getNameImpl() const #ifndef POCO_NO_THREADNAME std::string ThreadImpl::getOSThreadNameImpl() { - return isRunningImpl() ? getThreadName() : ""; + return isRunningImpl() ? getCurrentNameImpl() : ""; } #endif @@ -423,6 +368,71 @@ long ThreadImpl::currentOsTidImpl() } +#ifndef POCO_NO_THREADNAME + +namespace +{ + std::string truncateName(const std::string& name, std::size_t maxLen = POCO_MAX_THREAD_NAME_LEN) + { + if (name.size() > maxLen) + return name.substr(0, maxLen).append("~"); + return name; + } +} + + +void ThreadImpl::setCurrentNameImpl(const std::string& threadName) +{ +#if defined(POCO_OS_FAMILY_UNIX) && !defined(POCO_VXWORKS) + #if (POCO_OS == POCO_OS_FREE_BSD) + pthread_setname_np(pthread_self(), truncateName(threadName).c_str()); + #elif (POCO_OS == POCO_OS_MAC_OS_X) + #ifdef __MAC_OS_X_VERSION_MIN_REQUIRED + #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 + pthread_setname_np(truncateName(threadName).c_str()); + #endif + #endif + #elif (POCO_OS == POCO_OS_QNX) + pthread_setname_np(pthread_self(), truncateName(threadName, _NTO_THREAD_NAME_MAX).c_str()); + #else + prctl(PR_SET_NAME, truncateName(threadName).c_str()); + #endif +#endif +} + + +std::string ThreadImpl::getCurrentNameImpl() +{ +#if defined(POCO_OS_FAMILY_UNIX) && !defined(POCO_VXWORKS) + constexpr std::size_t nameSize = + #if (POCO_OS == POCO_OS_QNX) + _NTO_THREAD_NAME_MAX; + #else + POCO_MAX_THREAD_NAME_LEN; + #endif + char name[nameSize + 1]{'\0'}; + #if (POCO_OS == POCO_OS_FREE_BSD) + pthread_getname_np(pthread_self(), name, nameSize + 1); + #elif (POCO_OS == POCO_OS_MAC_OS_X) + #ifdef __MAC_OS_X_VERSION_MIN_REQUIRED + #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 + pthread_getname_np(pthread_self(), name, nameSize + 1); + #endif + #endif + #elif (POCO_OS == POCO_OS_QNX) + pthread_getname_np(pthread_self(), name, nameSize); + #else + prctl(PR_GET_NAME, name); + #endif + return name; +#else + return std::string(); +#endif +} + +#endif // POCO_NO_THREADNAME + + void* ThreadImpl::runnableEntry(void* pThread) { _currentThreadHolder.set(reinterpret_cast(pThread)); @@ -438,14 +448,14 @@ void* ThreadImpl::runnableEntry(void* pThread) ThreadImpl* pThreadImpl = reinterpret_cast(pThread); #ifndef POCO_NO_THREADNAME - setThreadName(reinterpret_cast(pThread)->getName()); + setCurrentNameImpl(reinterpret_cast(pThread)->getName()); #endif AutoPtr pData = pThreadImpl->_pData; #ifndef POCO_NO_THREADNAME { FastMutex::ScopedLock lock(pData->mutex); - setThreadName(pData->name); + setCurrentNameImpl(pData->name); } #endif diff --git a/Foundation/src/Thread_WIN32.cpp b/Foundation/src/Thread_WIN32.cpp index 20a23f5bd..1243a8141 100644 --- a/Foundation/src/Thread_WIN32.cpp +++ b/Foundation/src/Thread_WIN32.cpp @@ -223,6 +223,76 @@ long ThreadImpl::currentOsTidImpl() } +namespace +{ + // SetThreadDescription/GetThreadDescription require Windows 10 version 1607+ + // Use dynamic loading for compatibility with older Windows versions + using SetThreadDescriptionFunc = HRESULT (WINAPI *)(HANDLE, PCWSTR); + using GetThreadDescriptionFunc = HRESULT (WINAPI *)(HANDLE, PWSTR*); + + SetThreadDescriptionFunc getSetThreadDescriptionFunc() + { + static SetThreadDescriptionFunc func = reinterpret_cast( + GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "SetThreadDescription")); + return func; + } + + GetThreadDescriptionFunc getGetThreadDescriptionFunc() + { + static GetThreadDescriptionFunc func = reinterpret_cast( + GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "GetThreadDescription")); + return func; + } +} + + +void ThreadImpl::setCurrentNameImpl(const std::string& threadName) +{ + // Try modern API first (Windows 10 1607+) + auto setDesc = getSetThreadDescriptionFunc(); + if (setDesc) + { + int wideLen = MultiByteToWideChar(CP_UTF8, 0, threadName.c_str(), -1, NULL, 0); + if (wideLen > 0) + { + std::wstring wname(wideLen, L'\0'); + MultiByteToWideChar(CP_UTF8, 0, threadName.c_str(), -1, &wname[0], wideLen); + if (SUCCEEDED(setDesc(GetCurrentThread(), wname.c_str()))) + return; + } + } + + // Fall back to legacy exception method for older Windows/debuggers + setThreadName(static_cast(-1), threadName); +} + + +std::string ThreadImpl::getCurrentNameImpl() +{ + auto getDesc = getGetThreadDescriptionFunc(); + if (!getDesc) return std::string(); + + PWSTR data = nullptr; + HRESULT hr = getDesc(GetCurrentThread(), &data); + if (SUCCEEDED(hr) && data) + { + std::string result; + int convSize = WideCharToMultiByte(CP_UTF8, 0, data, -1, NULL, 0, NULL, NULL); + if (convSize > 0) + { + result.resize(convSize); + WideCharToMultiByte(CP_UTF8, 0, data, -1, &result[0], convSize, NULL, NULL); + // Remove null terminator from string + if (!result.empty() && result.back() == '\0') + result.pop_back(); + } + LocalFree(data); + return result; + } + return std::string(); +} + + bool ThreadImpl::setAffinityImpl(int affinity) { HANDLE hProcess = GetCurrentProcess(); diff --git a/Foundation/testsuite/src/LoggerTest.cpp b/Foundation/testsuite/src/LoggerTest.cpp index 6e815042f..1350eb8b7 100644 --- a/Foundation/testsuite/src/LoggerTest.cpp +++ b/Foundation/testsuite/src/LoggerTest.cpp @@ -14,13 +14,22 @@ #include "Poco/Logger.h" #include "Poco/AutoPtr.h" #include "TestChannel.h" - +#include "Poco/Thread.h" +#include "Poco/Event.h" +#include "Poco/PatternFormatter.h" +#include "Poco/FormattingChannel.h" +#include +#include +#include using Poco::Logger; using Poco::Channel; using Poco::Message; using Poco::AutoPtr; - +using Poco::PatternFormatter; +using Poco::FormattingChannel; +using Poco::Event; +using Poco::Thread; LoggerTest::LoggerTest(const std::string& name): CppUnit::TestCase(name) { @@ -289,6 +298,92 @@ void LoggerTest::testDump() pChannel->clear(); } +namespace ThreadNameTestStrings { +const std::string loggerName = "Logger"; +const std::string threadName = "ThreadName"; +const std::string message = "Test message"; +} + +template +std::string LoggerTest::doTestFormatThreadName(ThreadFactory makeThread) +{ + AutoPtr pChannel = new TestChannel; + AutoPtr fmt = new PatternFormatter("%s:%I:%T:%q:%t"); + AutoPtr pFmtChannel = new FormattingChannel(fmt, pChannel); + + Logger& logger = Logger::get(ThreadNameTestStrings::loggerName); + logger.setChannel(pFmtChannel); + logger.setLevel(Message::PRIO_INFORMATION); + + Event ev; + auto thr = makeThread( + ThreadNameTestStrings::threadName, + [&ev, &logger] { + logger.information(ThreadNameTestStrings::message); + ev.set(); + }); + ev.wait(); + + thr->join(); + + const std::string logMsg = pChannel->getLastMessage().getText(); + std::vector parts; + std::size_t p = 0; + while (p < logMsg.size()) + { + auto q = logMsg.find(':', p); + if (q == std::string::npos) + { + q = logMsg.size(); + } + parts.push_back(logMsg.substr(p, q - p)); + p = q + 1; + } + assertTrue (parts.size() >= 5); + assertEqual( ThreadNameTestStrings::loggerName, parts[0] ); + assertEqual( ThreadNameTestStrings::threadName, parts[2] ); + assertEqual( "I", parts[3] ); + assertEqual( ThreadNameTestStrings::message, parts[4] ); + + return parts[1]; +} + + +void LoggerTest::testFormatThreadName() +{ + Thread thr(ThreadNameTestStrings::threadName); + std::string expectedTid = std::to_string(thr.id()); + + std::string actualTid = doTestFormatThreadName( + [&thr](std::string, auto body) { + thr.startFunc(std::move(body)); + return &thr; + } + ); + assertEqual( expectedTid, actualTid ); +} + + +void LoggerTest::testFormatStdThreadName() +{ +#ifndef POCO_NO_THREADNAME + std::unique_ptr thrPtr; + std::string expectedTid; + std::string actualTid = doTestFormatThreadName( + [&thrPtr, &expectedTid](std::string name, auto bodyIn) { + thrPtr = std::make_unique( + [name, body = std::move(bodyIn), &expectedTid] { + expectedTid = std::to_string(Thread::currentOsTid()); + Thread::setCurrentName(name); + body(); + } + ); + return thrPtr.get(); + } + ); + assertEqual( expectedTid, actualTid ); +#endif +} void LoggerTest::setUp() { @@ -309,6 +404,8 @@ CppUnit::Test* LoggerTest::suite() CppUnit_addTest(pSuite, LoggerTest, testFormat); CppUnit_addTest(pSuite, LoggerTest, testFormatAny); CppUnit_addTest(pSuite, LoggerTest, testDump); + CppUnit_addTest(pSuite, LoggerTest, testFormatThreadName); + CppUnit_addTest(pSuite, LoggerTest, testFormatStdThreadName); return pSuite; } diff --git a/Foundation/testsuite/src/LoggerTest.h b/Foundation/testsuite/src/LoggerTest.h index 39fab3da8..209aa083b 100644 --- a/Foundation/testsuite/src/LoggerTest.h +++ b/Foundation/testsuite/src/LoggerTest.h @@ -16,7 +16,7 @@ #include "Poco/Foundation.h" #include "CppUnit/TestCase.h" - +#include class LoggerTest: public CppUnit::TestCase { @@ -28,6 +28,8 @@ public: void testFormat(); void testFormatAny(); void testDump(); + void testFormatThreadName(); + void testFormatStdThreadName(); void setUp(); void tearDown(); @@ -35,6 +37,8 @@ public: static CppUnit::Test* suite(); private: + template + std::string doTestFormatThreadName(ThreadFactory makeThread); }; diff --git a/cppignore.win b/cppignore.win index b72afc5d6..f2e5a8ae1 100644 --- a/cppignore.win +++ b/cppignore.win @@ -21,3 +21,8 @@ class CppUnit::TestCaller.testServiceReturnsTrueIfStopped class CppUnit::TestCaller.testSendToReceiveFrom class CppUnit::TestCaller.testMTU class CppUnit::TestCaller.testCachedSession +class CppUnit::TestCaller.testListener +class CppUnit::TestCaller.testChannelFacility +class CppUnit::TestCaller.testChannelOpenClose +class CppUnit::TestCaller.testOldBSD +class CppUnit::TestCaller.testStructuredData