/* Thread pool Copyright (C) 2019-2021 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" #if CXX_STD_THREAD #include "gdbsupport/thread-pool.h" #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 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 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 set_thread_name (int (*set_name) (const char *), const char *name) { set_name (name); } #endif /* USE_PTHREAD_SETNAME_NP */ 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) { 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; } std::future thread_pool::post_task (std::function &&func) { std::packaged_task t (std::move (func)); std::future f = t.get_future (); if (m_thread_count == 0) { /* Just execute it now. */ t (); } else { std::lock_guard guard (m_tasks_mutex); m_tasks.emplace (std::move (t)); m_tasks_cv.notify_one (); } return f; } void thread_pool::thread_function () { #ifdef USE_PTHREAD_SETNAME_NP /* This must be done here, because on macOS one can only set the name of the current thread. */ set_thread_name (pthread_setname_np, "gdb worker"); #endif /* 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 */