quill
ThreadUtilities.h
1 
7 #pragma once
8 
9 #include "quill/core/Attributes.h"
10 #include "quill/core/QuillError.h"
11 
12 #include <atomic>
13 #include <cstdint>
14 #include <cstring>
15 #include <string>
16 
17 #if defined(_WIN32)
18  #if !defined(WIN32_LEAN_AND_MEAN)
19  #define WIN32_LEAN_AND_MEAN
20  #endif
21 
22  #if !defined(NOMINMAX)
23  // Mingw already defines this, so no need to redefine
24  #define NOMINMAX
25  #endif
26 
27  #include <windows.h>
28 
29 #elif defined(__APPLE__)
30  #include <mach/thread_act.h>
31  #include <mach/thread_policy.h>
32  #include <pthread.h>
33 #elif defined(__NetBSD__)
34  #include <lwp.h>
35  #include <pthread.h>
36  #include <unistd.h>
37 #elif defined(__FreeBSD__)
38  #include <pthread_np.h>
39  #include <sys/thr.h>
40  #include <unistd.h>
41 #elif defined(__DragonFly__)
42  #include <pthread_np.h>
43  #include <sys/lwp.h>
44  #include <unistd.h>
45 #elif defined(__OpenBSD__)
46  #include <pthread_np.h>
47  #include <unistd.h>
48 #else
49  // linux
50  #include <pthread.h>
51  #include <sys/syscall.h>
52  #include <unistd.h>
53 #endif
54 
55 QUILL_BEGIN_NAMESPACE
56 
57 namespace detail
58 {
59 #if defined(_WIN32)
60 
66 QUILL_NODISCARD inline std::wstring s2ws(std::string const& str)
67 {
68  if (str.empty())
69  {
70  return std::wstring{};
71  }
72 
73  int const size_needed =
74  ::MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast<int>(str.size()), nullptr, 0);
75 
76  if (size_needed == 0)
77  {
78  return std::wstring{};
79  }
80 
81  std::wstring ret_val(static_cast<size_t>(size_needed), 0);
82  ::MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast<int>(str.size()), &ret_val[0], size_needed);
83  return ret_val;
84 }
85 
91 QUILL_NODISCARD inline std::string ws2s(std::wstring const& wstr)
92 {
93  if (wstr.empty())
94  {
95  return std::string{};
96  }
97 
98  int const size_needed = ::WideCharToMultiByte(
99  CP_UTF8, 0, wstr.data(), static_cast<int>(wstr.size()), nullptr, 0, nullptr, nullptr);
100 
101  if (size_needed == 0)
102  {
103  return std::string{};
104  }
105 
106  std::string ret_val(static_cast<size_t>(size_needed), 0);
107  ::WideCharToMultiByte(CP_UTF8, 0, wstr.data(), static_cast<int>(wstr.size()),
108  &ret_val[0], size_needed, nullptr, nullptr);
109  return ret_val;
110 }
111 
112 /***/
113 template <typename ReturnT, typename Signature, typename... Args>
114 ReturnT callRunTimeDynamicLinkedFunction(std::string const& dll_name,
115  std::string const& function_name, Args... args)
116 {
117  // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreaddescription
118  // Windows Server 2016, Windows 10 LTSB 2016 and Windows 10 version 1607:
119  // GetThreadDescription is only available by Run Time Dynamic Linking in KernelBase.dll.
120  // Use GetModuleHandleA since the target DLLs (e.g. KernelBase.dll) are always loaded.
121  HMODULE const hinstLibrary = ::GetModuleHandleA(dll_name.c_str());
122 
123  if (QUILL_UNLIKELY(hinstLibrary == nullptr))
124  {
125  QUILL_THROW(QuillError{std::string{"Failed to get module handle for " + dll_name}});
126  }
127 
128  // Using a C-style cast or memcpy to avoid Clang's strict function pointer casting rules
129  FARPROC proc_address = GetProcAddress(hinstLibrary, function_name.c_str());
130 
131  if (QUILL_UNLIKELY(proc_address == nullptr))
132  {
133  QUILL_THROW(QuillError{std::string{"Failed to call " + function_name + " " + dll_name}});
134  }
135 
136  // Use memcpy to avoid the strict cast warning in Clang
137  Signature callable;
138  std::memcpy(&callable, &proc_address, sizeof(proc_address));
139 
140  return callable(static_cast<Args&&>(args)...);
141 }
142 #endif
143 
150 QUILL_NODISCARD QUILL_EXPORT QUILL_ATTRIBUTE_USED inline std::string get_thread_name()
151 {
152 #if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(__ANDROID__) || \
153  defined(QUILL_NO_THREAD_NAME_SUPPORT)
154  // Disabled on MINGW / Cygwin.
155  return std::string{"ThreadNameDisabled"};
156 #elif defined(_WIN32)
157  PWSTR data = nullptr;
158  typedef HRESULT(WINAPI * GetThreadDescriptionSignature)(HANDLE, PWSTR*);
159 
160  // KernelBase.dll is always loaded in every Windows process
161  HMODULE const hinstLibrary = ::GetModuleHandleW(L"KernelBase.dll");
162 
163  if (!hinstLibrary)
164  {
165  return std::string{"ThreadNameDisabled"};
166  }
167 
168  FARPROC const proc_address = GetProcAddress(hinstLibrary, "GetThreadDescription");
169 
170  if (!proc_address)
171  {
172  return std::string{"ThreadNameDisabled"};
173  }
174 
175  GetThreadDescriptionSignature callable;
176  std::memcpy(&callable, &proc_address, sizeof(proc_address));
177 
178  HRESULT const hr = callable(GetCurrentThread(), &data);
179 
180  if (FAILED(hr) || QUILL_UNLIKELY(data == nullptr))
181  {
182  return std::string{"ThreadNameDisabled"};
183  }
184 
185  std::wstring const wide_name{data, wcsnlen_s(data, 256)};
186  LocalFree(data);
187  return ws2s(wide_name);
188 #else
189  // Apple, linux
190  char thread_name[16] = {'\0'};
191  #if defined(__OpenBSD__) || defined(__FreeBSD__)
192  pthread_get_name_np(pthread_self(), &thread_name[0], 16);
193  #else
194  auto res = pthread_getname_np(pthread_self(), &thread_name[0], 16);
195  if (res != 0)
196  {
197  QUILL_THROW(QuillError{"Failed to get thread name. error: " + std::to_string(res)});
198  }
199  #endif
200  return std::string{&thread_name[0], strlen(&thread_name[0])};
201 #endif
202 }
203 
204 #if defined(QUILL_USE_SEQUENTIAL_THREAD_ID)
205 extern std::atomic<uint32_t> g_next_thread_id;
206 extern QUILL_THREAD_LOCAL uint32_t g_cached_thread_id;
207 #endif
208 
213 QUILL_NODISCARD QUILL_EXPORT QUILL_ATTRIBUTE_USED inline uint32_t get_thread_id() noexcept
214 {
215 #if defined(QUILL_USE_SEQUENTIAL_THREAD_ID)
216  if (QUILL_LIKELY(g_cached_thread_id != 0u))
217  {
218  return g_cached_thread_id;
219  }
220 
221  g_cached_thread_id = g_next_thread_id.fetch_add(1u, std::memory_order_relaxed) + 1u;
222  return g_cached_thread_id;
223 #elif defined(__CYGWIN__)
224  // get thread id on cygwin not supported
225  return 0;
226 #elif defined(_WIN32)
227  return static_cast<uint32_t>(GetCurrentThreadId());
228 #elif defined(__linux__)
229  return static_cast<uint32_t>(::syscall(SYS_gettid));
230 #elif defined(__APPLE__)
231  uint64_t tid64;
232  pthread_threadid_np(nullptr, &tid64);
233  return static_cast<uint32_t>(tid64);
234 #elif defined(__NetBSD__)
235  return static_cast<uint32_t>(_lwp_self());
236 #elif defined(__FreeBSD__)
237  long lwpid;
238  thr_self(&lwpid);
239  return static_cast<uint32_t>(lwpid);
240 #elif defined(__DragonFly__)
241  return static_cast<uint32_t>(lwp_gettid());
242 #elif defined(__OpenBSD__)
243  return static_cast<uint32_t>(getthrid());
244 #else
245  return reinterpret_cast<uintptr_t>(pthread_self()); // (Ab)use pthread_self as a last resort option
246 #endif
247 }
248 
249 } // namespace detail
250 
251 QUILL_END_NAMESPACE
QUILL_NODISCARD QUILL_EXPORT QUILL_ATTRIBUTE_USED std::string get_thread_name()
Returns the name of the thread.
Definition: ThreadUtilities.h:150
Setups a signal handler to handle fatal signals.
Definition: BackendManager.h:28
custom exception
Definition: QuillError.h:47
QUILL_NODISCARD QUILL_EXPORT QUILL_ATTRIBUTE_USED uint32_t get_thread_id() noexcept
Returns the os assigned ID of the thread.
Definition: ThreadUtilities.h:213