xref: /netbsd-src/external/gpl3/gdb.old/dist/gdbsupport/thread-pool.cc (revision 6881a4007f077b54e5f51159c52b9b25f57deb0d)
17d62b00eSchristos /* Thread pool
27d62b00eSchristos 
3*6881a400Schristos    Copyright (C) 2019-2023 Free Software Foundation, Inc.
47d62b00eSchristos 
57d62b00eSchristos    This file is part of GDB.
67d62b00eSchristos 
77d62b00eSchristos    This program is free software; you can redistribute it and/or modify
87d62b00eSchristos    it under the terms of the GNU General Public License as published by
97d62b00eSchristos    the Free Software Foundation; either version 3 of the License, or
107d62b00eSchristos    (at your option) any later version.
117d62b00eSchristos 
127d62b00eSchristos    This program is distributed in the hope that it will be useful,
137d62b00eSchristos    but WITHOUT ANY WARRANTY; without even the implied warranty of
147d62b00eSchristos    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
157d62b00eSchristos    GNU General Public License for more details.
167d62b00eSchristos 
177d62b00eSchristos    You should have received a copy of the GNU General Public License
187d62b00eSchristos    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
197d62b00eSchristos 
207d62b00eSchristos #include "common-defs.h"
21*6881a400Schristos #include "gdbsupport/thread-pool.h"
227d62b00eSchristos 
237d62b00eSchristos #if CXX_STD_THREAD
247d62b00eSchristos 
257d62b00eSchristos #include "gdbsupport/alt-stack.h"
267d62b00eSchristos #include "gdbsupport/block-signals.h"
277d62b00eSchristos #include <algorithm>
28*6881a400Schristos #include <system_error>
297d62b00eSchristos 
307d62b00eSchristos /* On the off chance that we have the pthread library on a Windows
317d62b00eSchristos    host, but std::thread is not using it, avoid calling
327d62b00eSchristos    pthread_setname_np on Windows.  */
337d62b00eSchristos #ifndef _WIN32
347d62b00eSchristos #ifdef HAVE_PTHREAD_SETNAME_NP
357d62b00eSchristos #define USE_PTHREAD_SETNAME_NP
367d62b00eSchristos #endif
377d62b00eSchristos #endif
387d62b00eSchristos 
397d62b00eSchristos #ifdef USE_PTHREAD_SETNAME_NP
407d62b00eSchristos 
417d62b00eSchristos #include <pthread.h>
427d62b00eSchristos 
437d62b00eSchristos /* Handle platform discrepancies in pthread_setname_np: macOS uses a
447d62b00eSchristos    single-argument form, while Linux uses a two-argument form.  NetBSD
457d62b00eSchristos    takes a printf-style format and an argument.  This wrapper handles the
467d62b00eSchristos    difference.  */
477d62b00eSchristos 
487d62b00eSchristos ATTRIBUTE_UNUSED static void
49*6881a400Schristos do_set_thread_name (int (*set_name) (pthread_t, const char *, void *),
507d62b00eSchristos 		    const char *name)
517d62b00eSchristos {
527d62b00eSchristos   set_name (pthread_self (), "%s", const_cast<char *> (name));
537d62b00eSchristos }
547d62b00eSchristos 
557d62b00eSchristos ATTRIBUTE_UNUSED static void
56*6881a400Schristos do_set_thread_name (int (*set_name) (pthread_t, const char *),
57*6881a400Schristos 		    const char *name)
587d62b00eSchristos {
597d62b00eSchristos   set_name (pthread_self (), name);
607d62b00eSchristos }
617d62b00eSchristos 
627d62b00eSchristos /* The macOS man page says that pthread_setname_np returns "void", but
637d62b00eSchristos    the headers actually declare it returning "int".  */
647d62b00eSchristos ATTRIBUTE_UNUSED static void
65*6881a400Schristos do_set_thread_name (int (*set_name) (const char *), const char *name)
667d62b00eSchristos {
677d62b00eSchristos   set_name (name);
687d62b00eSchristos }
697d62b00eSchristos 
70*6881a400Schristos static void
71*6881a400Schristos set_thread_name (const char *name)
72*6881a400Schristos {
73*6881a400Schristos   do_set_thread_name (pthread_setname_np, name);
74*6881a400Schristos }
75*6881a400Schristos 
76*6881a400Schristos #elif defined (USE_WIN32API)
77*6881a400Schristos 
78*6881a400Schristos #include <windows.h>
79*6881a400Schristos 
80*6881a400Schristos typedef HRESULT WINAPI (SetThreadDescription_ftype) (HANDLE, PCWSTR);
81*6881a400Schristos static SetThreadDescription_ftype *dyn_SetThreadDescription;
82*6881a400Schristos static bool initialized;
83*6881a400Schristos 
84*6881a400Schristos static void
85*6881a400Schristos init_windows ()
86*6881a400Schristos {
87*6881a400Schristos   initialized = true;
88*6881a400Schristos 
89*6881a400Schristos   HMODULE hm = LoadLibrary (TEXT ("kernel32.dll"));
90*6881a400Schristos   if (hm)
91*6881a400Schristos     dyn_SetThreadDescription
92*6881a400Schristos       = (SetThreadDescription_ftype *) GetProcAddress (hm,
93*6881a400Schristos 						       "SetThreadDescription");
94*6881a400Schristos 
95*6881a400Schristos   /* On some versions of Windows, this function is only available in
96*6881a400Schristos      KernelBase.dll, not kernel32.dll.  */
97*6881a400Schristos   if (dyn_SetThreadDescription == nullptr)
98*6881a400Schristos     {
99*6881a400Schristos       hm = LoadLibrary (TEXT ("KernelBase.dll"));
100*6881a400Schristos       if (hm)
101*6881a400Schristos 	dyn_SetThreadDescription
102*6881a400Schristos 	  = (SetThreadDescription_ftype *) GetProcAddress (hm,
103*6881a400Schristos 							   "SetThreadDescription");
104*6881a400Schristos     }
105*6881a400Schristos }
106*6881a400Schristos 
107*6881a400Schristos static void
108*6881a400Schristos do_set_thread_name (const wchar_t *name)
109*6881a400Schristos {
110*6881a400Schristos   if (!initialized)
111*6881a400Schristos     init_windows ();
112*6881a400Schristos 
113*6881a400Schristos   if (dyn_SetThreadDescription != nullptr)
114*6881a400Schristos     dyn_SetThreadDescription (GetCurrentThread (), name);
115*6881a400Schristos }
116*6881a400Schristos 
117*6881a400Schristos #define set_thread_name(NAME) do_set_thread_name (L ## NAME)
118*6881a400Schristos 
119*6881a400Schristos #else /* USE_WIN32API */
120*6881a400Schristos 
121*6881a400Schristos static void
122*6881a400Schristos set_thread_name (const char *name)
123*6881a400Schristos {
124*6881a400Schristos }
125*6881a400Schristos 
126*6881a400Schristos #endif
127*6881a400Schristos 
128*6881a400Schristos #endif /* CXX_STD_THREAD */
1297d62b00eSchristos 
1307d62b00eSchristos namespace gdb
1317d62b00eSchristos {
1327d62b00eSchristos 
1337d62b00eSchristos /* The thread pool detach()s its threads, so that the threads will not
1347d62b00eSchristos    prevent the process from exiting.  However, it was discovered that
1357d62b00eSchristos    if any detached threads were still waiting on a condition variable,
1367d62b00eSchristos    then the condition variable's destructor would wait for the threads
1377d62b00eSchristos    to exit -- defeating the purpose.
1387d62b00eSchristos 
1397d62b00eSchristos    Allocating the thread pool on the heap and simply "leaking" it
1407d62b00eSchristos    avoids this problem.
1417d62b00eSchristos */
1427d62b00eSchristos thread_pool *thread_pool::g_thread_pool = new thread_pool ();
1437d62b00eSchristos 
1447d62b00eSchristos thread_pool::~thread_pool ()
1457d62b00eSchristos {
1467d62b00eSchristos   /* Because this is a singleton, we don't need to clean up.  The
1477d62b00eSchristos      threads are detached so that they won't prevent process exit.
1487d62b00eSchristos      And, cleaning up here would be actively harmful in at least one
1497d62b00eSchristos      case -- see the comment by the definition of g_thread_pool.  */
1507d62b00eSchristos }
1517d62b00eSchristos 
1527d62b00eSchristos void
1537d62b00eSchristos thread_pool::set_thread_count (size_t num_threads)
1547d62b00eSchristos {
155*6881a400Schristos #if CXX_STD_THREAD
1567d62b00eSchristos   std::lock_guard<std::mutex> guard (m_tasks_mutex);
1577d62b00eSchristos 
1587d62b00eSchristos   /* If the new size is larger, start some new threads.  */
1597d62b00eSchristos   if (m_thread_count < num_threads)
1607d62b00eSchristos     {
1617d62b00eSchristos       /* Ensure that signals used by gdb are blocked in the new
1627d62b00eSchristos 	 threads.  */
1637d62b00eSchristos       block_signals blocker;
1647d62b00eSchristos       for (size_t i = m_thread_count; i < num_threads; ++i)
1657d62b00eSchristos 	{
166*6881a400Schristos 	  try
167*6881a400Schristos 	    {
1687d62b00eSchristos 	      std::thread thread (&thread_pool::thread_function, this);
1697d62b00eSchristos 	      thread.detach ();
1707d62b00eSchristos 	    }
171*6881a400Schristos 	  catch (const std::system_error &)
172*6881a400Schristos 	    {
173*6881a400Schristos 	      /* libstdc++ may not implement std::thread, and will
174*6881a400Schristos 		 throw an exception on use.  It seems fine to ignore
175*6881a400Schristos 		 this, and any other sort of startup failure here.  */
176*6881a400Schristos 	      num_threads = i;
177*6881a400Schristos 	      break;
178*6881a400Schristos 	    }
179*6881a400Schristos 	}
1807d62b00eSchristos     }
1817d62b00eSchristos   /* If the new size is smaller, terminate some existing threads.  */
1827d62b00eSchristos   if (num_threads < m_thread_count)
1837d62b00eSchristos     {
1847d62b00eSchristos       for (size_t i = num_threads; i < m_thread_count; ++i)
1857d62b00eSchristos 	m_tasks.emplace ();
1867d62b00eSchristos       m_tasks_cv.notify_all ();
1877d62b00eSchristos     }
1887d62b00eSchristos 
1897d62b00eSchristos   m_thread_count = num_threads;
190*6881a400Schristos #else
191*6881a400Schristos   /* No threads available, simply ignore the request.  */
192*6881a400Schristos #endif /* CXX_STD_THREAD */
1937d62b00eSchristos }
1947d62b00eSchristos 
195*6881a400Schristos #if CXX_STD_THREAD
1967d62b00eSchristos 
197*6881a400Schristos void
198*6881a400Schristos thread_pool::do_post_task (std::packaged_task<void ()> &&func)
1997d62b00eSchristos {
200*6881a400Schristos   std::packaged_task<void ()> t (std::move (func));
201*6881a400Schristos 
202*6881a400Schristos   if (m_thread_count != 0)
2037d62b00eSchristos     {
2047d62b00eSchristos       std::lock_guard<std::mutex> guard (m_tasks_mutex);
2057d62b00eSchristos       m_tasks.emplace (std::move (t));
2067d62b00eSchristos       m_tasks_cv.notify_one ();
2077d62b00eSchristos     }
208*6881a400Schristos   else
209*6881a400Schristos     {
210*6881a400Schristos       /* Just execute it now.  */
211*6881a400Schristos       t ();
212*6881a400Schristos     }
2137d62b00eSchristos }
2147d62b00eSchristos 
2157d62b00eSchristos void
2167d62b00eSchristos thread_pool::thread_function ()
2177d62b00eSchristos {
2187d62b00eSchristos   /* This must be done here, because on macOS one can only set the
2197d62b00eSchristos      name of the current thread.  */
220*6881a400Schristos   set_thread_name ("gdb worker");
2217d62b00eSchristos 
2227d62b00eSchristos   /* Ensure that SIGSEGV is delivered to an alternate signal
2237d62b00eSchristos      stack.  */
2247d62b00eSchristos   gdb::alternate_signal_stack signal_stack;
2257d62b00eSchristos 
2267d62b00eSchristos   while (true)
2277d62b00eSchristos     {
228*6881a400Schristos       optional<task_t> t;
2297d62b00eSchristos 
2307d62b00eSchristos       {
2317d62b00eSchristos 	/* We want to hold the lock while examining the task list, but
2327d62b00eSchristos 	   not while invoking the task function.  */
2337d62b00eSchristos 	std::unique_lock<std::mutex> guard (m_tasks_mutex);
2347d62b00eSchristos 	while (m_tasks.empty ())
2357d62b00eSchristos 	  m_tasks_cv.wait (guard);
2367d62b00eSchristos 	t = std::move (m_tasks.front());
2377d62b00eSchristos 	m_tasks.pop ();
2387d62b00eSchristos       }
2397d62b00eSchristos 
2407d62b00eSchristos       if (!t.has_value ())
2417d62b00eSchristos 	break;
2427d62b00eSchristos       (*t) ();
2437d62b00eSchristos     }
2447d62b00eSchristos }
2457d62b00eSchristos 
2467d62b00eSchristos #endif /* CXX_STD_THREAD */
247*6881a400Schristos 
248*6881a400Schristos } /* namespace gdb */
249