[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