aboutsummaryrefslogtreecommitdiff
path: root/gdb/run-on-main-thread.c
blob: 746ea35ee9e6c4f87fa5c0b58f1a416e9a075842 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/* 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 <http://www.gnu.org/licenses/>.  */

#include "run-on-main-thread.h"
#include "ser-event.h"
#if CXX_STD_THREAD
#include <thread>
#include <mutex>
#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<std::function<void ()>> 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<std::function<void ()>> local;

  /* Hold the lock while changing the globals, but not while running
     the runnables.  */
  {
#if CXX_STD_THREAD
    std::lock_guard<std::mutex> 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<void ()> &&func)
{
#if CXX_STD_THREAD
  std::lock_guard<std::mutex> 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 ();
    });
}