diff --git a/include/wx/thread.h b/include/wx/thread.h index 3a4303d669..ca3db2db72 100644 --- a/include/wx/thread.h +++ b/include/wx/thread.h @@ -597,6 +597,9 @@ public: virtual ~wxThread(); protected: + // sets name to assist debugging + bool SetName(const wxString &name); + // exits from the current thread - can be called only from this thread void Exit(ExitCode exitcode = NULL); diff --git a/interface/wx/thread.h b/interface/wx/thread.h index 96ed7dcdd8..9c60ad0cf0 100644 --- a/interface/wx/thread.h +++ b/interface/wx/thread.h @@ -1385,6 +1385,28 @@ protected: OnExit() will be called just before exiting. */ void Exit(ExitCode exitcode = 0); + + /** + Sets an internal name for the thread, which enables the debugger to + show the name along with the list of threads, as long as both the OS + and the debugger support this. The thread name may also be visible + in list of processes and in crash dumps (also in release builds). + + This function is protected, as it should be called from a derived + class, in the context of the derived thread. A good place to call this + function is at the beginning of your Entry() function. + + For portable code, the name should be in ASCII. On Linux the length + is truncated to 15 characters, on other platforms the name can be + longer than that. + + @return Either: + - @false: Failure or missing implementation for this OS. + - @true: Success or undetectable failure. + + @since 3.1.6 + */ + bool SetName(const wxString &name); }; diff --git a/samples/thread/thread.cpp b/samples/thread/thread.cpp index 6c26d0f8d5..6484097b95 100644 --- a/samples/thread/thread.cpp +++ b/samples/thread/thread.cpp @@ -919,6 +919,10 @@ MyThread::~MyThread() wxThread::ExitCode MyThread::Entry() { + // setting thread name helps with debugging, as the debugger + // may be able to show thread names along with the list of threads. + SetName("My Thread"); + wxLogMessage("Thread started (priority = %u).", GetPriority()); for ( m_count = 0; m_count < 10; m_count++ ) @@ -970,6 +974,10 @@ void MyWorkerThread::OnExit() wxThread::ExitCode MyWorkerThread::Entry() { + // setting thread name helps with debugging, as the debugger + // may be able to show thread names along with the list of threads. + SetName("Worker Thread"); + #if TEST_YIELD_RACE_CONDITION if ( TestDestroy() ) return NULL; diff --git a/src/msw/thread.cpp b/src/msw/thread.cpp index 2b22bd61c2..79acec9bfb 100644 --- a/src/msw/thread.cpp +++ b/src/msw/thread.cpp @@ -37,6 +37,7 @@ #include "wx/msw/seh.h" #include "wx/except.h" +#include "wx/dynlib.h" // must have this symbol defined to get _beginthread/_endthread declarations #ifndef _MT @@ -1121,6 +1122,90 @@ wxThreadError wxThread::Kill() return rc; } +// At least MSVC 2017 version 15.6 is required for observing the +// thread names set using this method. +// Windows 10 version 1607 is required for the SetThreadDescription +// function. +static bool wxSetThreadNameOnWindows10(const WCHAR *threadName) +{ + typedef HRESULT(WINAPI* SetThreadDescription_t)(HANDLE, PCWSTR); + static SetThreadDescription_t s_pfnSetThreadDescription = NULL; + + static bool s_initDone = false; + if ( !s_initDone ) + { + wxLoadedDLL dllKernel32("kernel32.dll"); + wxDL_INIT_FUNC(s_pfn, SetThreadDescription, dllKernel32); + s_initDone = true; + } + + if ( s_pfnSetThreadDescription ) + { + HRESULT r = s_pfnSetThreadDescription(GetCurrentThread(), threadName); + if (SUCCEEDED(r)) + return true; + else + wxLogApiError("SetThreadDescription", r); + } + return false; +} + +#ifdef _MSC_VER +// This function works with all MSVC versions. +static bool wxSetThreadNameOnAnyMSVC(const char* threadName) +{ + // This implementation is taken almost verbatim from: + // https://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx + + const DWORD MS_VC_EXCEPTION = 0x406D1388; +#pragma pack(push,8) + typedef struct tagTHREADNAME_INFO + { + DWORD dwType; // Must be 0x1000. + LPCSTR szName; // Pointer to name (in user addr space). + DWORD dwThreadID; // Thread ID (-1=caller thread). + DWORD dwFlags; // Reserved for future use, must be zero. + } THREADNAME_INFO; +#pragma pack(pop) + + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = threadName; + info.dwThreadID = (DWORD)-1; + info.dwFlags = 0; +#pragma warning(push) +#pragma warning(disable: 6320 6322) + __try + { + RaiseException(MS_VC_EXCEPTION, 0, + sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info); + return true; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + } +#pragma warning(pop) + return false; +} +#endif // MSC_VER + +bool wxThread::SetName(const wxString &name) +{ + wxCHECK_MSG(this == This(), false, + "SetName() must be called in the context of the thread to be named"); + + bool retval = wxSetThreadNameOnWindows10(name.wc_str()); + + // Even if the method above succeeded, we can set + // the name through this other, independent way also. +#ifdef _MSC_VER + retval |= wxSetThreadNameOnAnyMSVC(name.c_str()); +#endif + + // return true if at least one call succeeded + return retval; +} + void wxThread::Exit(ExitCode status) { wxThreadInternal::DoThreadOnExit(this); diff --git a/src/unix/threadpsx.cpp b/src/unix/threadpsx.cpp index d2be4c1184..fdf2af59bb 100644 --- a/src/unix/threadpsx.cpp +++ b/src/unix/threadpsx.cpp @@ -1680,6 +1680,33 @@ wxThreadError wxThread::Kill() } } +bool wxThread::SetName(const wxString &name) +{ + wxCHECK_MSG(this == This(), false, + "SetName() must be called from inside the thread to be named"); + + + // the API is nearly the same on different *nix, but not quite: + +#if defined(__DARWIN__) + pthread_setname_np(name.utf8_str()); + return true; +#elif defined(__LINUX__) + // Linux doesn't allow names longer than 15 bytes. + char truncatedName[16] = { 0 }; + strncpy(truncatedName, name.utf8_str(), 15); + + return pthread_setname_np(pthread_self(), truncatedName) == 0; +#else + wxLogDebug("No implementation for wxThread::SetName() on this OS."); + return false; +#endif + // TODO: #elif defined(__FREEBSD__) || defined(__OPENBSD__) + // TODO: These two BSDs would need #include + // and the function call would be: + // pthread_set_name_np(pthread_self(), name.utf8_str()); +} + void wxThread::Exit(ExitCode status) { wxASSERT_MSG( This() == this,