|  | /* Thread pool | 
|  |  | 
|  | Copyright (C) 2019-2022 Free Software Foundation, Inc. | 
|  |  | 
|  | This file is part of GDB. | 
|  |  | 
|  | This program is free software; you can redistribute it and/or modify | 
|  | it under the terms of the GNU General Public License as published by | 
|  | the Free Software Foundation; either version 3 of the License, or | 
|  | (at your option) any later version. | 
|  |  | 
|  | This program is distributed in the hope that it will be useful, | 
|  | but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|  | GNU General Public License for more details. | 
|  |  | 
|  | You should have received a copy of the GNU General Public License | 
|  | along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ | 
|  |  | 
|  | #include "common-defs.h" | 
|  | #include "gdbsupport/thread-pool.h" | 
|  |  | 
|  | #if CXX_STD_THREAD | 
|  |  | 
|  | #include "gdbsupport/alt-stack.h" | 
|  | #include "gdbsupport/block-signals.h" | 
|  | #include <algorithm> | 
|  | #include <system_error> | 
|  |  | 
|  | /* On the off chance that we have the pthread library on a Windows | 
|  | host, but std::thread is not using it, avoid calling | 
|  | pthread_setname_np on Windows.  */ | 
|  | #ifndef _WIN32 | 
|  | #ifdef HAVE_PTHREAD_SETNAME_NP | 
|  | #define USE_PTHREAD_SETNAME_NP | 
|  | #endif | 
|  | #endif | 
|  |  | 
|  | #ifdef USE_PTHREAD_SETNAME_NP | 
|  |  | 
|  | #include <pthread.h> | 
|  |  | 
|  | /* Handle platform discrepancies in pthread_setname_np: macOS uses a | 
|  | single-argument form, while Linux uses a two-argument form.  NetBSD | 
|  | takes a printf-style format and an argument.  This wrapper handles the | 
|  | difference.  */ | 
|  |  | 
|  | ATTRIBUTE_UNUSED static void | 
|  | do_set_thread_name (int (*set_name) (pthread_t, const char *, void *), | 
|  | const char *name) | 
|  | { | 
|  | set_name (pthread_self (), "%s", const_cast<char *> (name)); | 
|  | } | 
|  |  | 
|  | ATTRIBUTE_UNUSED static void | 
|  | do_set_thread_name (int (*set_name) (pthread_t, const char *), | 
|  | const char *name) | 
|  | { | 
|  | set_name (pthread_self (), name); | 
|  | } | 
|  |  | 
|  | /* The macOS man page says that pthread_setname_np returns "void", but | 
|  | the headers actually declare it returning "int".  */ | 
|  | ATTRIBUTE_UNUSED static void | 
|  | do_set_thread_name (int (*set_name) (const char *), const char *name) | 
|  | { | 
|  | set_name (name); | 
|  | } | 
|  |  | 
|  | static void | 
|  | set_thread_name (const char *name) | 
|  | { | 
|  | do_set_thread_name (pthread_setname_np, name); | 
|  | } | 
|  |  | 
|  | #elif defined (USE_WIN32API) | 
|  |  | 
|  | #include <windows.h> | 
|  |  | 
|  | typedef HRESULT WINAPI (SetThreadDescription_ftype) (HANDLE, PCWSTR); | 
|  | static SetThreadDescription_ftype *dyn_SetThreadDescription; | 
|  | static bool initialized; | 
|  |  | 
|  | static void | 
|  | init_windows () | 
|  | { | 
|  | initialized = true; | 
|  |  | 
|  | HMODULE hm = LoadLibrary (TEXT ("kernel32.dll")); | 
|  | if (hm) | 
|  | dyn_SetThreadDescription | 
|  | = (SetThreadDescription_ftype *) GetProcAddress (hm, | 
|  | "SetThreadDescription"); | 
|  |  | 
|  | /* On some versions of Windows, this function is only available in | 
|  | KernelBase.dll, not kernel32.dll.  */ | 
|  | if (dyn_SetThreadDescription == nullptr) | 
|  | { | 
|  | hm = LoadLibrary (TEXT ("KernelBase.dll")); | 
|  | if (hm) | 
|  | dyn_SetThreadDescription | 
|  | = (SetThreadDescription_ftype *) GetProcAddress (hm, | 
|  | "SetThreadDescription"); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void | 
|  | do_set_thread_name (const wchar_t *name) | 
|  | { | 
|  | if (!initialized) | 
|  | init_windows (); | 
|  |  | 
|  | if (dyn_SetThreadDescription != nullptr) | 
|  | dyn_SetThreadDescription (GetCurrentThread (), name); | 
|  | } | 
|  |  | 
|  | #define set_thread_name(NAME) do_set_thread_name (L ## NAME) | 
|  |  | 
|  | #else /* USE_WIN32API */ | 
|  |  | 
|  | static void | 
|  | set_thread_name (const char *name) | 
|  | { | 
|  | } | 
|  |  | 
|  | #endif | 
|  |  | 
|  | #endif /* CXX_STD_THREAD */ | 
|  |  | 
|  | namespace gdb | 
|  | { | 
|  |  | 
|  | /* The thread pool detach()s its threads, so that the threads will not | 
|  | prevent the process from exiting.  However, it was discovered that | 
|  | if any detached threads were still waiting on a condition variable, | 
|  | then the condition variable's destructor would wait for the threads | 
|  | to exit -- defeating the purpose. | 
|  |  | 
|  | Allocating the thread pool on the heap and simply "leaking" it | 
|  | avoids this problem. | 
|  | */ | 
|  | thread_pool *thread_pool::g_thread_pool = new thread_pool (); | 
|  |  | 
|  | thread_pool::~thread_pool () | 
|  | { | 
|  | /* Because this is a singleton, we don't need to clean up.  The | 
|  | threads are detached so that they won't prevent process exit. | 
|  | And, cleaning up here would be actively harmful in at least one | 
|  | case -- see the comment by the definition of g_thread_pool.  */ | 
|  | } | 
|  |  | 
|  | void | 
|  | thread_pool::set_thread_count (size_t num_threads) | 
|  | { | 
|  | #if CXX_STD_THREAD | 
|  | std::lock_guard<std::mutex> guard (m_tasks_mutex); | 
|  |  | 
|  | /* If the new size is larger, start some new threads.  */ | 
|  | if (m_thread_count < num_threads) | 
|  | { | 
|  | /* Ensure that signals used by gdb are blocked in the new | 
|  | threads.  */ | 
|  | block_signals blocker; | 
|  | for (size_t i = m_thread_count; i < num_threads; ++i) | 
|  | { | 
|  | try | 
|  | { | 
|  | std::thread thread (&thread_pool::thread_function, this); | 
|  | thread.detach (); | 
|  | } | 
|  | catch (const std::system_error &) | 
|  | { | 
|  | /* libstdc++ may not implement std::thread, and will | 
|  | throw an exception on use.  It seems fine to ignore | 
|  | this, and any other sort of startup failure here.  */ | 
|  | num_threads = i; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | /* If the new size is smaller, terminate some existing threads.  */ | 
|  | if (num_threads < m_thread_count) | 
|  | { | 
|  | for (size_t i = num_threads; i < m_thread_count; ++i) | 
|  | m_tasks.emplace (); | 
|  | m_tasks_cv.notify_all (); | 
|  | } | 
|  |  | 
|  | m_thread_count = num_threads; | 
|  | #else | 
|  | /* No threads available, simply ignore the request.  */ | 
|  | #endif /* CXX_STD_THREAD */ | 
|  | } | 
|  |  | 
|  | #if CXX_STD_THREAD | 
|  |  | 
|  | void | 
|  | thread_pool::do_post_task (std::packaged_task<void ()> &&func) | 
|  | { | 
|  | std::packaged_task<void ()> t (std::move (func)); | 
|  |  | 
|  | if (m_thread_count != 0) | 
|  | { | 
|  | std::lock_guard<std::mutex> guard (m_tasks_mutex); | 
|  | m_tasks.emplace (std::move (t)); | 
|  | m_tasks_cv.notify_one (); | 
|  | } | 
|  | else | 
|  | { | 
|  | /* Just execute it now.  */ | 
|  | t (); | 
|  | } | 
|  | } | 
|  |  | 
|  | void | 
|  | thread_pool::thread_function () | 
|  | { | 
|  | /* This must be done here, because on macOS one can only set the | 
|  | name of the current thread.  */ | 
|  | set_thread_name ("gdb worker"); | 
|  |  | 
|  | /* Ensure that SIGSEGV is delivered to an alternate signal | 
|  | stack.  */ | 
|  | gdb::alternate_signal_stack signal_stack; | 
|  |  | 
|  | while (true) | 
|  | { | 
|  | optional<task_t> t; | 
|  |  | 
|  | { | 
|  | /* We want to hold the lock while examining the task list, but | 
|  | not while invoking the task function.  */ | 
|  | std::unique_lock<std::mutex> guard (m_tasks_mutex); | 
|  | while (m_tasks.empty ()) | 
|  | m_tasks_cv.wait (guard); | 
|  | t = std::move (m_tasks.front()); | 
|  | m_tasks.pop (); | 
|  | } | 
|  |  | 
|  | if (!t.has_value ()) | 
|  | break; | 
|  | (*t) (); | 
|  | } | 
|  | } | 
|  |  | 
|  | #endif /* CXX_STD_THREAD */ | 
|  |  | 
|  | } /* namespace gdb */ |