Add wxThread::SetName for naming threads for debugging purposes

Such thread names can be shown by some debuggers, and depending on
the OS and compiler versions used, they can be visible in process
lists and crash dumps.

Co-authored-by: PB <PBforDev@gmail.com>
This commit is contained in:
Lauri Nurmi 2021-10-24 14:02:56 +03:00
parent 9cc0c9a082
commit fc756d06a6
5 changed files with 145 additions and 0 deletions

View File

@ -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);

View File

@ -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);
};

View File

@ -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;

View File

@ -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);

View File

@ -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 <pthread_np.h>
// and the function call would be:
// pthread_set_name_np(pthread_self(), name.utf8_str());
}
void wxThread::Exit(ExitCode status)
{
wxASSERT_MSG( This() == this,