Skip to content

Commit

Permalink
COMMON: Add ability to set thread names
Browse files Browse the repository at this point in the history
Does not work on all platforms, but is a no-op where it doesn't.
  • Loading branch information
clone2727 authored and DrMcCoy committed Aug 28, 2020
1 parent b4d02de commit 04c6902
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 0 deletions.
120 changes: 120 additions & 0 deletions src/common/thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,26 @@
#include "src/common/thread.h"
#include "src/common/util.h"

// Include whatever headers are necessary to allow for naming the thread
#if defined(__linux__)
#include <sys/prctl.h>
#include <cstring>
#elif defined(__APPLE__)
#include <dlfcn.h>
#elif defined(__MINGW32__) || defined(__FreeBSD__) || defined(__OpenBSD__)
#include <pthread.h>
#endif

#if defined(_WIN32)
#include <boost/scope_exit.hpp>

#define WIN32_LEAN_AND_MEAN
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#endif

namespace Common {

Thread::Thread() : _killThread(false), _threadRunning(false) {
Expand Down Expand Up @@ -102,6 +122,9 @@ int Thread::threadHelper(void *obj) {
// The thread is running.
thread->_threadRunning.store(true, std::memory_order_relaxed);

// Attempt to set the thread name
setCurrentThreadName(thread->_name);

// Run the thread
thread->threadMethod();

Expand All @@ -111,4 +134,101 @@ int Thread::threadHelper(void *obj) {
return 0;
}

#ifdef _MSC_VER

namespace {

// This needs to be a separate function because MSVC is too incompetent to handle
// both SEH and an unwind in the same function.
void setCurrentThreadNameSEH(const Common::UString &name) {
// To set the name in the debugger, we need to use SEH. Use the non-header-defined
// struct. It has to be aligned on an 8 byte boundary, even on 32-bit.
struct alignas(8) {
DWORD dwType;
LPCSTR szName;
DWORD dwThreadID;
DWORD dwFlags;
} info;

// Fill with desired values
info.dwType = 0x1000;
info.szName = name.c_str();
info.dwThreadID = -1;
info.dwFlags = 0;

__try {
RaiseException(0x406D1388, 0, sizeof(info) / sizeof(ULONG_PTR), reinterpret_cast<ULONG_PTR *>(&info));
} __except (EXCEPTION_EXECUTE_HANDLER) {
// Do nothing
}
}

} // End of anonymous namespace

#endif

void Thread::setCurrentThreadName(const Common::UString &name) {
#if defined(__linux__)
// We need to fit into a 16 byte array
char buffer[16];
size_t length = std::min(name.size(), sizeof(buffer) - 1);
memcpy(buffer, name.c_str(), length);
buffer[length] = '\0';

// Invoke prctl with PR_SET_NAME. This is slightly more portable than
// pthread_setname_np on Linux, but the end result is identical.
prctl(PR_SET_NAME, buffer);
#elif defined(__APPLE__)
// pthread_setname_np is available on 10.6+
typedef void (*SetNameFunc)(const char *);
SetNameFunc func = (SetNameFunc)dlsym(RTLD_DEFAULT, "pthread_setname_np");
if (func)
func(name.c_str());
#elif defined(__FreeBSD__) || defined(__OpenBSD__)
pthread_set_name_np(pthread_self(), name.c_str());
#elif defined(_WIN32)
#ifdef _MSC_VER
setCurrentThreadNameSEH(name);
#else
// MinGW's pthreads implementation provides pthread_setname_np. All this
// basically wraps the above MSVC code into the pthread API. We could manually
// set up the exception handler and use it for both, but it's a major pain.
pthread_setname_np(pthread_self(), name.c_str());
#endif

// On Windows 10, there's another way to set the thread -- the SetThreadDescription
// function. Let's see if we have it available. Get the kernel32.dll handle.
// This is a different name from the above exception method.
HMODULE kernel32 = LoadLibraryA("kernel32.dll");
if (!kernel32)
return;

// Set it to destroy upon exit
BOOST_SCOPE_EXIT(kernel32) {
FreeLibrary(kernel32);
} BOOST_SCOPE_EXIT_END;

typedef HRESULT (WINAPI *SetThreadDescriptionFunc)(HANDLE hThread, PCWSTR lpThreadDescription);
SetThreadDescriptionFunc func = (SetThreadDescriptionFunc)GetProcAddress(kernel32, "SetThreadDescription");
if (!func)
return;

// Need to convert to a wide char string. First, query the size.
int result = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, name.c_str(), name.size(), nullptr, 0);
if (!result)
return;

// Now do the actual conversion.
std::unique_ptr<wchar_t[]> buffer = std::make_unique<wchar_t[]>(result + 1);
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, name.c_str(), name.size(), buffer.get(), result);
buffer[result] = L'\0';

// Actually invoke the function
func(GetCurrentThread(), buffer.get());
#else
// Do nothing; silence the unused warning
(void)name;
#endif
}

} // End of namespace Common
7 changes: 7 additions & 0 deletions src/common/thread.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ class Thread : boost::noncopyable {
bool createThread(const UString &name = "");
bool destroyThread();

/**
* Set the name of the thread, if available on the platform.
* Otherwise, a no-op. If the name is longer than the platform
* supports, it is truncated to fit.
*/
static void setCurrentThreadName(const Common::UString &name);

protected:
std::atomic<bool> _killThread;

Expand Down

0 comments on commit 04c6902

Please sign in to comment.