/* Thread pool Copyright (C) 2019-2023 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 . */ #include "common-defs.h" #include "gdbsupport/thread-pool.h" #if CXX_STD_THREAD #include "gdbsupport/alt-stack.h" #include "gdbsupport/block-signals.h" #include #include /* 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 /* 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 (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 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 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 &&func) { std::packaged_task t (std::move (func)); if (m_thread_count != 0) { std::lock_guard 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 t; { /* We want to hold the lock while examining the task list, but not while invoking the task function. */ std::unique_lock 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 */