xref: /netbsd-src/external/gpl3/gdb/dist/gdbsupport/thread-pool.cc (revision 5ba1f45f2a09259cc846f20c7c5501604d633c90)
18dffb485Schristos /* Thread pool
28dffb485Schristos 
3*5ba1f45fSchristos    Copyright (C) 2019-2024 Free Software Foundation, Inc.
48dffb485Schristos 
58dffb485Schristos    This file is part of GDB.
68dffb485Schristos 
78dffb485Schristos    This program is free software; you can redistribute it and/or modify
88dffb485Schristos    it under the terms of the GNU General Public License as published by
98dffb485Schristos    the Free Software Foundation; either version 3 of the License, or
108dffb485Schristos    (at your option) any later version.
118dffb485Schristos 
128dffb485Schristos    This program is distributed in the hope that it will be useful,
138dffb485Schristos    but WITHOUT ANY WARRANTY; without even the implied warranty of
148dffb485Schristos    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
158dffb485Schristos    GNU General Public License for more details.
168dffb485Schristos 
178dffb485Schristos    You should have received a copy of the GNU General Public License
188dffb485Schristos    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
198dffb485Schristos 
204b169a6bSchristos #include "gdbsupport/thread-pool.h"
218dffb485Schristos 
228dffb485Schristos #if CXX_STD_THREAD
238dffb485Schristos 
248dffb485Schristos #include "gdbsupport/alt-stack.h"
258dffb485Schristos #include "gdbsupport/block-signals.h"
268dffb485Schristos #include <algorithm>
274b169a6bSchristos #include <system_error>
288dffb485Schristos 
298dffb485Schristos /* On the off chance that we have the pthread library on a Windows
308dffb485Schristos    host, but std::thread is not using it, avoid calling
318dffb485Schristos    pthread_setname_np on Windows.  */
328dffb485Schristos #ifndef _WIN32
338dffb485Schristos #ifdef HAVE_PTHREAD_SETNAME_NP
348dffb485Schristos #define USE_PTHREAD_SETNAME_NP
358dffb485Schristos #endif
368dffb485Schristos #endif
378dffb485Schristos 
388dffb485Schristos #ifdef USE_PTHREAD_SETNAME_NP
398dffb485Schristos 
408dffb485Schristos #include <pthread.h>
418dffb485Schristos 
428dffb485Schristos /* Handle platform discrepancies in pthread_setname_np: macOS uses a
438dffb485Schristos    single-argument form, while Linux uses a two-argument form.  NetBSD
448dffb485Schristos    takes a printf-style format and an argument.  This wrapper handles the
458dffb485Schristos    difference.  */
468dffb485Schristos 
478dffb485Schristos ATTRIBUTE_UNUSED static void
484b169a6bSchristos do_set_thread_name (int (*set_name) (pthread_t, const char *, void *),
498dffb485Schristos 		    const char *name)
508dffb485Schristos {
518dffb485Schristos   set_name (pthread_self (), "%s", const_cast<char *> (name));
528dffb485Schristos }
538dffb485Schristos 
548dffb485Schristos ATTRIBUTE_UNUSED static void
554b169a6bSchristos do_set_thread_name (int (*set_name) (pthread_t, const char *),
564b169a6bSchristos 		    const char *name)
578dffb485Schristos {
588dffb485Schristos   set_name (pthread_self (), name);
598dffb485Schristos }
608dffb485Schristos 
618dffb485Schristos /* The macOS man page says that pthread_setname_np returns "void", but
628dffb485Schristos    the headers actually declare it returning "int".  */
638dffb485Schristos ATTRIBUTE_UNUSED static void
644b169a6bSchristos do_set_thread_name (int (*set_name) (const char *), const char *name)
658dffb485Schristos {
668dffb485Schristos   set_name (name);
678dffb485Schristos }
688dffb485Schristos 
694b169a6bSchristos static void
704b169a6bSchristos set_thread_name (const char *name)
714b169a6bSchristos {
724b169a6bSchristos   do_set_thread_name (pthread_setname_np, name);
734b169a6bSchristos }
744b169a6bSchristos 
754b169a6bSchristos #elif defined (USE_WIN32API)
764b169a6bSchristos 
774b169a6bSchristos #include <windows.h>
784b169a6bSchristos 
794b169a6bSchristos typedef HRESULT WINAPI (SetThreadDescription_ftype) (HANDLE, PCWSTR);
804b169a6bSchristos static SetThreadDescription_ftype *dyn_SetThreadDescription;
814b169a6bSchristos static bool initialized;
824b169a6bSchristos 
834b169a6bSchristos static void
844b169a6bSchristos init_windows ()
854b169a6bSchristos {
864b169a6bSchristos   initialized = true;
874b169a6bSchristos 
884b169a6bSchristos   HMODULE hm = LoadLibrary (TEXT ("kernel32.dll"));
894b169a6bSchristos   if (hm)
904b169a6bSchristos     dyn_SetThreadDescription
914b169a6bSchristos       = (SetThreadDescription_ftype *) GetProcAddress (hm,
924b169a6bSchristos 						       "SetThreadDescription");
934b169a6bSchristos 
944b169a6bSchristos   /* On some versions of Windows, this function is only available in
954b169a6bSchristos      KernelBase.dll, not kernel32.dll.  */
964b169a6bSchristos   if (dyn_SetThreadDescription == nullptr)
974b169a6bSchristos     {
984b169a6bSchristos       hm = LoadLibrary (TEXT ("KernelBase.dll"));
994b169a6bSchristos       if (hm)
1004b169a6bSchristos 	dyn_SetThreadDescription
1014b169a6bSchristos 	  = (SetThreadDescription_ftype *) GetProcAddress (hm,
1024b169a6bSchristos 							   "SetThreadDescription");
1034b169a6bSchristos     }
1044b169a6bSchristos }
1054b169a6bSchristos 
1064b169a6bSchristos static void
1074b169a6bSchristos do_set_thread_name (const wchar_t *name)
1084b169a6bSchristos {
1094b169a6bSchristos   if (!initialized)
1104b169a6bSchristos     init_windows ();
1114b169a6bSchristos 
1124b169a6bSchristos   if (dyn_SetThreadDescription != nullptr)
1134b169a6bSchristos     dyn_SetThreadDescription (GetCurrentThread (), name);
1144b169a6bSchristos }
1154b169a6bSchristos 
1164b169a6bSchristos #define set_thread_name(NAME) do_set_thread_name (L ## NAME)
1174b169a6bSchristos 
1184b169a6bSchristos #else /* USE_WIN32API */
1194b169a6bSchristos 
1204b169a6bSchristos static void
1214b169a6bSchristos set_thread_name (const char *name)
1224b169a6bSchristos {
1234b169a6bSchristos }
1244b169a6bSchristos 
1254b169a6bSchristos #endif
1264b169a6bSchristos 
1274b169a6bSchristos #endif /* CXX_STD_THREAD */
1288dffb485Schristos 
1298dffb485Schristos namespace gdb
1308dffb485Schristos {
1318dffb485Schristos 
1328dffb485Schristos /* The thread pool detach()s its threads, so that the threads will not
1338dffb485Schristos    prevent the process from exiting.  However, it was discovered that
1348dffb485Schristos    if any detached threads were still waiting on a condition variable,
1358dffb485Schristos    then the condition variable's destructor would wait for the threads
1368dffb485Schristos    to exit -- defeating the purpose.
1378dffb485Schristos 
1388dffb485Schristos    Allocating the thread pool on the heap and simply "leaking" it
1398dffb485Schristos    avoids this problem.
1408dffb485Schristos */
1418dffb485Schristos thread_pool *thread_pool::g_thread_pool = new thread_pool ();
1428dffb485Schristos 
1438dffb485Schristos thread_pool::~thread_pool ()
1448dffb485Schristos {
1458dffb485Schristos   /* Because this is a singleton, we don't need to clean up.  The
1468dffb485Schristos      threads are detached so that they won't prevent process exit.
1478dffb485Schristos      And, cleaning up here would be actively harmful in at least one
1488dffb485Schristos      case -- see the comment by the definition of g_thread_pool.  */
1498dffb485Schristos }
1508dffb485Schristos 
1518dffb485Schristos void
1528dffb485Schristos thread_pool::set_thread_count (size_t num_threads)
1538dffb485Schristos {
1544b169a6bSchristos #if CXX_STD_THREAD
1558dffb485Schristos   std::lock_guard<std::mutex> guard (m_tasks_mutex);
156*5ba1f45fSchristos   m_sized_at_least_once = true;
1578dffb485Schristos 
1588dffb485Schristos   /* If the new size is larger, start some new threads.  */
1598dffb485Schristos   if (m_thread_count < num_threads)
1608dffb485Schristos     {
1618dffb485Schristos       /* Ensure that signals used by gdb are blocked in the new
1628dffb485Schristos 	 threads.  */
1638dffb485Schristos       block_signals blocker;
1648dffb485Schristos       for (size_t i = m_thread_count; i < num_threads; ++i)
1658dffb485Schristos 	{
1664b169a6bSchristos 	  try
1674b169a6bSchristos 	    {
1688dffb485Schristos 	      std::thread thread (&thread_pool::thread_function, this);
1698dffb485Schristos 	      thread.detach ();
1708dffb485Schristos 	    }
1714b169a6bSchristos 	  catch (const std::system_error &)
1724b169a6bSchristos 	    {
1734b169a6bSchristos 	      /* libstdc++ may not implement std::thread, and will
1744b169a6bSchristos 		 throw an exception on use.  It seems fine to ignore
1754b169a6bSchristos 		 this, and any other sort of startup failure here.  */
1764b169a6bSchristos 	      num_threads = i;
1774b169a6bSchristos 	      break;
1784b169a6bSchristos 	    }
1794b169a6bSchristos 	}
1808dffb485Schristos     }
1818dffb485Schristos   /* If the new size is smaller, terminate some existing threads.  */
1828dffb485Schristos   if (num_threads < m_thread_count)
1838dffb485Schristos     {
1848dffb485Schristos       for (size_t i = num_threads; i < m_thread_count; ++i)
1858dffb485Schristos 	m_tasks.emplace ();
1868dffb485Schristos       m_tasks_cv.notify_all ();
1878dffb485Schristos     }
1888dffb485Schristos 
1898dffb485Schristos   m_thread_count = num_threads;
1904b169a6bSchristos #else
1914b169a6bSchristos   /* No threads available, simply ignore the request.  */
1924b169a6bSchristos #endif /* CXX_STD_THREAD */
1938dffb485Schristos }
1948dffb485Schristos 
1954b169a6bSchristos #if CXX_STD_THREAD
1968dffb485Schristos 
1974b169a6bSchristos void
1984b169a6bSchristos thread_pool::do_post_task (std::packaged_task<void ()> &&func)
1998dffb485Schristos {
200*5ba1f45fSchristos   /* This assert is here to check that no tasks are posted to the pool between
201*5ba1f45fSchristos      its initialization and sizing.  */
202*5ba1f45fSchristos   gdb_assert (m_sized_at_least_once);
2034b169a6bSchristos   std::packaged_task<void ()> t (std::move (func));
2044b169a6bSchristos 
2054b169a6bSchristos   if (m_thread_count != 0)
2068dffb485Schristos     {
2078dffb485Schristos       std::lock_guard<std::mutex> guard (m_tasks_mutex);
2088dffb485Schristos       m_tasks.emplace (std::move (t));
2098dffb485Schristos       m_tasks_cv.notify_one ();
2108dffb485Schristos     }
2114b169a6bSchristos   else
2124b169a6bSchristos     {
2134b169a6bSchristos       /* Just execute it now.  */
2144b169a6bSchristos       t ();
2154b169a6bSchristos     }
2168dffb485Schristos }
2178dffb485Schristos 
2188dffb485Schristos void
2198dffb485Schristos thread_pool::thread_function ()
2208dffb485Schristos {
2218dffb485Schristos   /* This must be done here, because on macOS one can only set the
2228dffb485Schristos      name of the current thread.  */
2234b169a6bSchristos   set_thread_name ("gdb worker");
2248dffb485Schristos 
2258dffb485Schristos   /* Ensure that SIGSEGV is delivered to an alternate signal
2268dffb485Schristos      stack.  */
2278dffb485Schristos   gdb::alternate_signal_stack signal_stack;
2288dffb485Schristos 
2298dffb485Schristos   while (true)
2308dffb485Schristos     {
231*5ba1f45fSchristos       std::optional<task_t> t;
2328dffb485Schristos 
2338dffb485Schristos       {
2348dffb485Schristos 	/* We want to hold the lock while examining the task list, but
2358dffb485Schristos 	   not while invoking the task function.  */
2368dffb485Schristos 	std::unique_lock<std::mutex> guard (m_tasks_mutex);
2378dffb485Schristos 	while (m_tasks.empty ())
2388dffb485Schristos 	  m_tasks_cv.wait (guard);
2398dffb485Schristos 	t = std::move (m_tasks.front());
2408dffb485Schristos 	m_tasks.pop ();
2418dffb485Schristos       }
2428dffb485Schristos 
2438dffb485Schristos       if (!t.has_value ())
2448dffb485Schristos 	break;
2458dffb485Schristos       (*t) ();
2468dffb485Schristos     }
2478dffb485Schristos }
2488dffb485Schristos 
2498dffb485Schristos #endif /* CXX_STD_THREAD */
2504b169a6bSchristos 
2514b169a6bSchristos } /* namespace gdb */
252