/* Run a function on the main thread Copyright (C) 2019-2024 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 "run-on-main-thread.h" #include "ser-event.h" #if CXX_STD_THREAD #include #include #endif #include "gdbsupport/event-loop.h" /* The serial event used when posting runnables. */ static struct serial_event *runnable_event; /* Runnables that have been posted. */ static std::vector> runnables; #if CXX_STD_THREAD /* Mutex to hold when handling RUNNABLE_EVENT or RUNNABLES. */ static std::mutex runnable_mutex; /* The main thread's thread id. */ static std::thread::id main_thread_id; #endif /* Run all the queued runnables. */ static void run_events (int error, gdb_client_data client_data) { std::vector> local; /* Hold the lock while changing the globals, but not while running the runnables. */ { #if CXX_STD_THREAD std::lock_guard lock (runnable_mutex); #endif /* Clear the event fd. Do this before flushing the events list, so that any new event post afterwards is sure to re-awaken the event loop. */ serial_event_clear (runnable_event); /* Move the vector in case running a runnable pushes a new runnable. */ local = std::move (runnables); } for (auto &item : local) { try { item (); } catch (const gdb_exception_forced_quit &e) { /* GDB is terminating, so: - make sure this is propagated, and - no need to keep running things, so propagate immediately. */ throw; } catch (const gdb_exception_quit &e) { /* Should cancelation of a runnable event cancel the execution of the following one? The answer is not clear, so keep doing what we've done so far: ignore this exception. */ } catch (const gdb_exception &) { /* Ignore exceptions in the callback. */ } } } /* See run-on-main-thread.h. */ void run_on_main_thread (std::function &&func) { #if CXX_STD_THREAD std::lock_guard lock (runnable_mutex); #endif runnables.emplace_back (std::move (func)); serial_event_set (runnable_event); } #if CXX_STD_THREAD static bool main_thread_id_initialized = false; #endif /* See run-on-main-thread.h. */ bool is_main_thread () { #if CXX_STD_THREAD /* Initialize main_thread_id on first use of is_main_thread. */ if (!main_thread_id_initialized) { main_thread_id_initialized = true; main_thread_id = std::this_thread::get_id (); } return std::this_thread::get_id () == main_thread_id; #else return true; #endif } void _initialize_run_on_main_thread (); void _initialize_run_on_main_thread () { #if CXX_STD_THREAD /* The variable main_thread_id should be initialized when entering main, or at an earlier use, so it should already be initialized here. */ gdb_assert (main_thread_id_initialized); /* Assume that we execute this in the main thread. */ gdb_assert (is_main_thread ()); #endif runnable_event = make_serial_event (); add_file_handler (serial_event_fd (runnable_event), run_events, nullptr, "run-on-main-thread"); /* A runnable may refer to an extension language. So, we want to make sure any pending ones have been deleted before the extension languages are shut down. */ add_final_cleanup ([] () { #if CXX_STD_THREAD std::lock_guard lock (runnable_mutex); #endif runnables.clear (); }); }