[python] integrate with gdb event loop

Tom Tromey tromey@redhat.com
Wed Dec 10 15:27:00 GMT 2008


In multi-threaded operation, we need a way to run code in the gdb main
loop.  This new method tells gdb to run a function in the main thread
at some later point.

I used a pipe and a file event handler here, and not an async event
handler, because AFAICT an async handler has no way to interrupt the
main thread when it is in poll.  I suppose I could have sent a signal,
but this approach seems less risky.

Tom

2008-12-10  Tom Tromey  <tromey@redhat.com>

	* python/python.c: Include event-loop.h.
	(struct gdbpy_event): New type.
	(gdbpy_event_list, gdbpy_event_list_end): New globals.
	(gdbpy_event_fd): Likewise.
	(gdbpy_run_events): New function.
	(gdbpy_post_event): Likewise.
	(gdbpy_initialize_events): Likewise.
	(_initialize_python): Call it.
	(GdbMethods) <post_event>: New method.

2008-12-10  Tom Tromey  <tromey@redhat.com>

	* gdb.texinfo (Basic Python): Document post_event.

diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 8057812..ea6698c 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -18119,6 +18119,21 @@ evaluate it, and return the result as a @code{gdb.Value}.
 @var{expression} must be a string.
 @end defun
 
+@findex gdb.post_event
+@defun post_event event
+Put @var{event}, a callable object taking no arguments, into
+@value{GDBN}'s internal event queue.  This callable will be invoked at
+some later point, during @value{GDBN}'s event processing.  Events
+posted using @code{post_event} will be run in the order in which they
+were posted; however, there is no way to know when they will be
+processed relative to other events inside @value{GDBN}.
+
+@value{GDBN} is not thread-safe.  If your Python program uses multiple
+threads, you must be careful to only call @value{GDBN}-specific
+functions in the main @value{GDBN} thread.  @code{post_event} ensures
+this.
+@end defun
+
 @findex gdb.write
 @defun write string
 Print a string to @value{GDBN}'s paginated standard output stream.
diff --git a/gdb/python/python.c b/gdb/python/python.c
index 3bf3d54..1432e5e 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -27,6 +27,7 @@
 #include "gdb_regex.h"
 #include "language.h"
 #include "valprint.h"
+#include "event-loop.h"
 
 #include <ctype.h>
 
@@ -416,6 +417,113 @@ gdbpy_parse_and_eval (PyObject *self, PyObject *args)
 
 
 
+/* Posting and handling events.  */
+
+/* A single event.  */
+struct gdbpy_event
+{
+  /* The Python event.  This is just a callable object.  */
+  PyObject *event;
+  /* The next event.  */
+  struct gdbpy_event *next;
+};
+
+/* All pending events.  */
+static struct gdbpy_event *gdbpy_event_list;
+/* The final link of the event list.  */
+static struct gdbpy_event **gdbpy_event_list_end;
+
+/* We use a file handler, and not an async handler, so that we can
+   wake up the main thread even when it is blocked in poll().  */
+static int gdbpy_event_fds[2];
+
+/* The file handler callback.  This reads from the internal pipe, and
+   then processes the Python event queue.  This will always be run in
+   the main gdb thread.  */
+static void
+gdbpy_run_events (int err, gdb_client_data ignore)
+{
+  PyGILState_STATE state;
+  char buffer[100];
+  int r;
+
+  state = PyGILState_Ensure ();
+
+  /* Just read whatever is available on the fd.  It is relatively
+     harmless if there are any bytes left over.  */
+  r = read (gdbpy_event_fds[0], buffer, sizeof (buffer));
+
+  while (gdbpy_event_list)
+    {
+      /* Dispatching the event might push a new element onto the event
+	 loop, so we update here "atomically enough".  */
+      struct gdbpy_event *item = gdbpy_event_list;
+      gdbpy_event_list = gdbpy_event_list->next;
+      if (gdbpy_event_list == NULL)
+	gdbpy_event_list_end = &gdbpy_event_list;
+
+      /* Ignore errors.  */
+      PyObject_CallObject (item->event, NULL);
+
+      Py_DECREF (item->event);
+      xfree (item);
+    }
+
+  PyGILState_Release (state);
+}
+
+/* Submit an event to the gdb thread.  */
+static PyObject *
+gdbpy_post_event (PyObject *self, PyObject *args)
+{
+  struct gdbpy_event *event;
+  PyObject *func;
+  int wakeup;
+
+  if (!PyArg_ParseTuple (args, "O", &func))
+    return NULL;
+
+  if (!PyCallable_Check (func))
+    {
+      PyErr_SetString (PyExc_RuntimeError, "Posted event is not callable");
+      return NULL;
+    }
+
+  Py_INCREF (func);
+
+  /* From here until the end of the function, we have the GIL, so we
+     can operate on our global data structures without worrying.  */
+  wakeup = gdbpy_event_list == NULL;
+
+  event = XNEW (struct gdbpy_event);
+  event->event = func;
+  event->next = NULL;
+  *gdbpy_event_list_end = event;
+  gdbpy_event_list_end = &event->next;
+
+  /* Wake up gdb when needed.  */
+  if (wakeup)
+    {
+      char c = 'q';		/* Anything. */
+      write (gdbpy_event_fds[1], &c, 1);
+    }
+
+  Py_RETURN_NONE;
+}
+
+/* Initialize the Python event handler.  */
+static void
+gdbpy_initialize_events (void)
+{
+  if (!pipe (gdbpy_event_fds))
+    {
+      gdbpy_event_list_end = &gdbpy_event_list;
+      add_file_handler (gdbpy_event_fds[0], gdbpy_run_events, NULL);
+    }
+}
+
+
+
 /* Threads.  */
 
 /* Callback function for use with iterate_over_threads.  This function
@@ -1285,6 +1393,8 @@ Enables or disables auto-loading of Python code when an object is opened."),
   gdbpy_initialize_parameters ();
   gdbpy_initialize_objfile ();
 
+  gdbpy_initialize_events ();
+
   PyRun_SimpleString ("import gdb");
   PyRun_SimpleString ("gdb.pretty_printers = {}");
 
@@ -1397,6 +1507,9 @@ Note: may later change to return an object." },
   { "parse_and_eval", gdbpy_parse_and_eval, METH_VARARGS,
     "Parse a string as an expression, evaluate it, and return the result." },
 
+  { "post_event", gdbpy_post_event, METH_VARARGS,
+    "Post an event into gdb's event loop." },
+
   { "write", gdbpy_write, METH_VARARGS,
     "Write a string using gdb's filtered stream." },
   { "flush", gdbpy_flush, METH_NOARGS,



More information about the Archer mailing list