1 | /* |
2 | * Copyright 2017-present Facebook, Inc. |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * You may obtain a copy of the License at |
7 | * |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * |
10 | * Unless required by applicable law or agreed to in writing, software |
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. |
15 | */ |
16 | |
17 | #include <folly/system/ThreadName.h> |
18 | |
19 | #include <type_traits> |
20 | |
21 | #include <folly/Portability.h> |
22 | #include <folly/Traits.h> |
23 | #include <folly/portability/PThread.h> |
24 | #include <folly/portability/Windows.h> |
25 | |
26 | namespace folly { |
27 | |
28 | // This looks a bit weird, but it's necessary to avoid |
29 | // having an undefined compiler function called. |
30 | #if defined(__GLIBC__) && !defined(__APPLE__) && !defined(__ANDROID__) |
31 | #if __GLIBC_PREREQ(2, 12) |
32 | // has pthread_setname_np(pthread_t, const char*) (2 params) |
33 | #define FOLLY_HAS_PTHREAD_SETNAME_NP_THREAD_NAME 1 |
34 | #endif |
35 | #endif |
36 | |
37 | #if defined(__APPLE__) |
38 | #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && \ |
39 | __MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 |
40 | // macOS 10.6+ has pthread_setname_np(const char*) (1 param) |
41 | #define FOLLY_HAS_PTHREAD_SETNAME_NP_NAME 1 |
42 | #elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && \ |
43 | __IPHONE_OS_VERSION_MIN_REQUIRED >= 30200 |
44 | // iOS 3.2+ has pthread_setname_np(const char*) (1 param) |
45 | #define FOLLY_HAS_PTHREAD_SETNAME_NP_NAME 1 |
46 | #endif |
47 | #endif // defined(__APPLE__) |
48 | |
49 | namespace { |
50 | |
51 | #if FOLLY_HAVE_PTHREAD && !_WIN32 |
52 | pthread_t stdTidToPthreadId(std::thread::id tid) { |
53 | static_assert( |
54 | std::is_same<pthread_t, std::thread::native_handle_type>::value, |
55 | "This assumes that the native handle type is pthread_t" ); |
56 | static_assert( |
57 | sizeof(std::thread::native_handle_type) == sizeof(std::thread::id), |
58 | "This assumes std::thread::id is a thin wrapper around " |
59 | "std::thread::native_handle_type, but that doesn't appear to be true." ); |
60 | // In most implementations, std::thread::id is a thin wrapper around |
61 | // std::thread::native_handle_type, which means we can do unsafe things to |
62 | // extract it. |
63 | pthread_t id; |
64 | std::memcpy(&id, &tid, sizeof(id)); |
65 | return id; |
66 | } |
67 | #endif |
68 | |
69 | } // namespace |
70 | |
71 | bool canSetCurrentThreadName() { |
72 | #if FOLLY_HAS_PTHREAD_SETNAME_NP_THREAD_NAME || \ |
73 | FOLLY_HAS_PTHREAD_SETNAME_NP_NAME || _WIN32 |
74 | return true; |
75 | #else |
76 | return false; |
77 | #endif |
78 | } |
79 | |
80 | bool canSetOtherThreadName() { |
81 | #if FOLLY_HAS_PTHREAD_SETNAME_NP_THREAD_NAME || _WIN32 |
82 | return true; |
83 | #else |
84 | return false; |
85 | #endif |
86 | } |
87 | |
88 | static constexpr size_t kMaxThreadNameLength = 16; |
89 | |
90 | Optional<std::string> getThreadName(std::thread::id id) { |
91 | #if FOLLY_HAS_PTHREAD_SETNAME_NP_THREAD_NAME || \ |
92 | FOLLY_HAS_PTHREAD_SETNAME_NP_NAME |
93 | std::array<char, kMaxThreadNameLength> buf; |
94 | if (pthread_getname_np(stdTidToPthreadId(id), buf.data(), buf.size()) != 0) { |
95 | return Optional<std::string>(); |
96 | } |
97 | return folly::make_optional(std::string(buf.data())); |
98 | #else |
99 | // There's not actually a way to get the thread name on Windows because |
100 | // thread names are a concept managed by the debugger, not the runtime. |
101 | return Optional<std::string>(); |
102 | #endif |
103 | } |
104 | |
105 | Optional<std::string> getCurrentThreadName() { |
106 | return getThreadName(std::this_thread::get_id()); |
107 | } |
108 | |
109 | bool setThreadName(std::thread::id tid, StringPiece name) { |
110 | auto trimmedName = name.subpiece(0, kMaxThreadNameLength - 1).str(); |
111 | #if _WIN32 |
112 | static_assert( |
113 | sizeof(unsigned int) == sizeof(std::thread::id), |
114 | "This assumes std::thread::id is a thin wrapper around " |
115 | "the thread id as an unsigned int, but that doesn't appear to be true." ); |
116 | |
117 | // http://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx |
118 | #pragma pack(push, 8) |
119 | struct THREADNAME_INFO { |
120 | DWORD dwType; // Must be 0x1000 |
121 | LPCSTR szName; // Pointer to name (in user address space) |
122 | DWORD dwThreadID; // Thread ID (-1 for caller thread) |
123 | DWORD dwFlags; // Reserved for future use; must be zero |
124 | }; |
125 | union TNIUnion { |
126 | THREADNAME_INFO tni; |
127 | ULONG_PTR upArray[4]; |
128 | }; |
129 | #pragma pack(pop) |
130 | |
131 | static constexpr DWORD kMSVCException = 0x406D1388; |
132 | |
133 | // std::thread::id is a thin wrapper around an integral thread id, |
134 | // so just extract the ID. |
135 | unsigned int id; |
136 | std::memcpy(&id, &tid, sizeof(id)); |
137 | |
138 | TNIUnion tniUnion = {0x1000, trimmedName.data(), id, 0}; |
139 | // This has to be in a separate stack frame from trimmedName, which requires |
140 | // C++ object destruction semantics. |
141 | return [&]() { |
142 | __try { |
143 | RaiseException(kMSVCException, 0, 4, tniUnion.upArray); |
144 | } __except ( |
145 | GetExceptionCode() == kMSVCException ? EXCEPTION_CONTINUE_EXECUTION |
146 | : EXCEPTION_EXECUTE_HANDLER) { |
147 | // Swallow the exception when a debugger isn't attached. |
148 | } |
149 | return true; |
150 | }(); |
151 | #else |
152 | name = name.subpiece(0, kMaxThreadNameLength - 1); |
153 | char buf[kMaxThreadNameLength] = {}; |
154 | std::memcpy(buf, name.data(), name.size()); |
155 | auto id = stdTidToPthreadId(tid); |
156 | #if FOLLY_HAS_PTHREAD_SETNAME_NP_THREAD_NAME |
157 | return 0 == pthread_setname_np(id, buf); |
158 | #elif FOLLY_HAS_PTHREAD_SETNAME_NP_NAME |
159 | // Since macOS 10.6 and iOS 3.2 it is possible for a thread to set its own |
160 | // name, but not that of some other thread. |
161 | if (pthread_equal(pthread_self(), id)) { |
162 | return 0 == pthread_setname_np(buf); |
163 | } |
164 | return false; |
165 | #else |
166 | (void)id; |
167 | return false; |
168 | #endif |
169 | #endif |
170 | } |
171 | |
172 | bool setThreadName(pthread_t pid, StringPiece name) { |
173 | #if _WIN32 |
174 | static_assert( |
175 | sizeof(unsigned int) == sizeof(std::thread::id), |
176 | "This assumes std::thread::id is a thin wrapper around " |
177 | "the thread id as an unsigned int, but that doesn't appear to be true." ); |
178 | |
179 | // std::thread::id is a thin wrapper around an integral thread id, |
180 | // so just stick the ID in. |
181 | unsigned int tid = pthread_getw32threadid_np(pid); |
182 | std::thread::id id; |
183 | std::memcpy(&id, &tid, sizeof(id)); |
184 | return setThreadName(id, name); |
185 | #else |
186 | static_assert( |
187 | std::is_same<pthread_t, std::thread::native_handle_type>::value, |
188 | "This assumes that the native handle type is pthread_t" ); |
189 | static_assert( |
190 | sizeof(std::thread::native_handle_type) == sizeof(std::thread::id), |
191 | "This assumes std::thread::id is a thin wrapper around " |
192 | "std::thread::native_handle_type, but that doesn't appear to be true." ); |
193 | // In most implementations, std::thread::id is a thin wrapper around |
194 | // std::thread::native_handle_type, which means we can do unsafe things to |
195 | // extract it. |
196 | std::thread::id id; |
197 | std::memcpy(static_cast<void*>(&id), &pid, sizeof(id)); |
198 | return setThreadName(id, name); |
199 | #endif |
200 | } |
201 | |
202 | bool setThreadName(StringPiece name) { |
203 | return setThreadName(std::this_thread::get_id(), name); |
204 | } |
205 | } // namespace folly |
206 | |