Re: [Python-Dev] Running GUI and "GObject.mainloop.run()" together?

Thanks Simon.
Thanks for the extensive info; however it needs some hours (if not days :P) to be digested.
On Tue, Dec 25, 2012 at 9:24 PM, Simon McVittie < simon.mcvittie@collabora.co.uk> wrote:
On 24/12/12 08:26, Ajay Garg wrote:
For a recap of the brief history, I have a parent process, that is spawning a child process via "subprocess". Currently, the child-process is a GUI process; however, I intend to "behave" it as a dbus-service as well.
In general that is something that can work, but it's necessary to understand a bit about how main loops work, and how the modules of your process deal with a main loop.
Just saying "GUI" is not very informative: there are dozens of GUI frameworks that you might be using, each with their own requirements and oddities. If you say Gtk, or Qt, or Tk, or Windows MFC, or whatever specific GUI framework you're using, then it becomes possible to say something concrete about your situation.
Based on later mails in the thread you seem to be using Gtk.
I should note here that you seem to be using PyGtk (the "traditional" Gtk 2 Python binding), which is deprecated. The modern version is to use PyGI, the Python GObject-Introspection binding, and Gtk 3.
When using PyGI, you have a choice of two D-Bus implementations: either GDBus (part of gi.repository.GIO), or dbus-python ("import dbus"). I would recommend GDBus, since dbus-python is constrained by backwards compatibility with some flawed design decisions.
However, assuming you're stuck with dbus-python:
I then used composition, wherein another class, "RemoteListener" deriving from "dbus.service.Object" was made an attribute of the "main" class. That worked. However, when I do
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) GObject.mainloop.run()
in the "RemoteListener"'s __init__ method, the GUI of the "main" class fails to load (apparently, because the "mainloop.run()" causes the singular, main-thread to go into busy-wait).
Almost; it's not a busy-wait. GObject.mainloop.run() is the equivalent of this pseudocode:
def run(self): while not global_default_main_context.someone_has_called_quit: if global_default_main_context.has_more_events(): global_default_main_context.process_next_event() else: global_default_main_context.wait_for_an_event()
so it will loop until someone calls GObject.mainloop.quit() or equivalent, or forever if that never happens - but as long as nothing "interesting" happens, it will block on a poll() or select() syscall in what my pseudocode calls wait_for_an_event(), which is the right thing to do in event-driven programming like GLib/Gtk.
(If you replace the last line of my pseudocode with "continue", that would be a busy-wait.)
I tried option b), but now instantiating "RemoteListener" in a separate thread
It is unclear whether the dbus-glib main loop glue (as set up by DBusGMainLoop) is thread-safe or not. The safest assumption is always "if you don't know whether foo is thread-safe, it probably isn't". In any case, if it *is* thread-safe, the subset of it that's exposed through dbus-python isn't enough to use it in multiple threads.
GDBus, as made available via PyGI (specifically, gi.repository.GIO), is known to be thread-safe.
Is there a way to run GUI and a dbus-service together?
The general answer: only if either the GUI and the D-Bus code run in different threads, or if they run in the same thread and can be made to share a main context.
The specific answer for Gtk: yes, they can easily share a main context.
This:
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
sets up dbus-python's mainloop integration to integrate with the global default main-context in GLib (implementation detail: it currently uses dbus-glib to do that). What that means is that whenever a D-Bus connection started by dbus-python wants to listen for events on a socket, or wait for a timeout, it will ask GLib to add those to the global default main context as event sources.
This:
GObject.mainloop.run()
iterates GLib's global default main context, analogous to the pseudocode I mentioned before. Any "interesting" events that happen will cause your code to be executed.
A typical GUI application also needs to run the main loop to wait for events. In PyGtk, you'd typically do that with:
Gtk.main()
Gtk also uses GLib's global default main context, so this is pretty similar to GObject.mainloop.run() - if you just remove the call to GObject.mainloop.run() and use Gtk.main() instead, everything should be fine.
As per http://www.pygtk.org/pygtk2reference/class- gobjectmainloop.html, it seems that we must be able to add event sources to gobject.mainloop
Yes. For instance, gobject.timeout_add(), gobject.idle_add() and gobject.io_add_watch() all add event sources to the default main context.
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) tells dbus-python that when it needs to add an event source to "the" main loop, it should use equivalent C functions in GLib to do so.
(In principle, DBusGMainLoop ought to take a GObject.MainContext as an optional argument - but that's never been implemented, and it currently always uses the default main context, which is the same one Gtk uses, and which should only be iterated from the main thread.)
Once the event sources are added, each instance of gobject.mainloop (in its particular thread), will cater to only those sources.
No, that's not true; gobject.mainloop is a namespace for a set of global functions, not an object. If you must use multiple threads (not recommended), please see the GLib C API documentation for details of how main loops and main contexts relate, then the PyGtk documentation to see how that translates into Python.
How is dbus."mainloop.glib.DBusGMainLoop(set_as_default=True)" related to gobject.mainloop?
It instantiates a new DBusGMainLoop and sets it as dbus-python's global default main-loop-integration object. (With hindsight, DBusGMainLoop was a poor choice of name - it should have been DBusGMainIntegration or something.) The result is that whenever a new dbus.connection.Connection is instantiated, it will call methods on that DBusGMainLoop to connect its event sources up to the default GLib main context, which is the same one used by Gtk.
dbus.bus.BusConnection, dbus.Bus, dbus.SessionBus etc. are dbus.connection.Connection subclasses, so anything I say about dbus.connection.Connection applies equally to them.
How is dbus."mainloop.glib.DBusGMainLoop(set_as_default=False)" related to gobject.mainloop?
It instantiates a new DBusGMainLoop and doesn't use it for anything. If you save the returned DBusGMainLoop in a variable (e.g. my_dbus_g_main_loop = DBusGMainLoop(...)), then you can pass a keyword argument mainloop=my_dbus_g_main_loop to a dbus.connection.Connection constructor, and that dbus.connection.Connection will use that DBusGMainLoop instead of dbus-python's global default. In practice, only a very unusual application would need to do that.
There is currently no point in having more than one DBusGMainLoop; it would become useful if dbus-glib was thread-safe, and if dbus-python supported non-default GLib main-contexts.
Is it necessary at all to specify "mainloop.glib.DBusGMainLoop(set_as_default=True)" or "mainloop.glib.DBusGMainLoop(set_as_default=False)" when using gobject.mainloop?
Yes. Otherwise, dbus-python has no way to know that your application is going to be iterating the GLib main loop, as opposed to Qt or Tk or Enlightenment or something.
currently for the client, I am having the (client) (parent) process run the command "dbus-send" via the python-subprocess API. Does there exist a python API to do it in a cleaner manner?
Yes, either dbus-python or GDBus. Each of those can do everything dbus-send can, and more.
For a start, could you please point me to the paradigm to send a dbus-signal from the client to the server (where the server has the "add_to_signal_receiver" been set up).
From the limited googling that I did, I remember someone saying that for
sending a signal, the typical setting-up-of-a-proxy-object is not required; however, I could not hit upon the exact dbus-python mechanism to send a signal :-\
S
dbus mailing list dbus@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dbus

Also, I think I am now starting to get a hang of things; however, one doubt solved raises another doubt :D
The reason I began looking out for the two-threads-approach, is because when trying to use the GUI (Gtk) application as a dbus-service, I was getting the error "This connection was not provided by any of .service files". I now see that the reason of it was :: I wasn't using "dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)" prior to the statement "Gtk.main()". Now, by your helpful guidance, wherein you stated that "Gtk.main()" has the same effect as "GObject.MainLoop.run()", I added the statement "dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)" before "Gtk.main()", and voila.. now I could get a proxy object, and invoke methods remotely. Thus, two threads are now not needed.
However, it now raises a new doubt : in my second approach, wherein I used "add_to_signal_receiver" (at the server side), and dbus-send (at the client side), how come things worked now, since I did not add "dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)" in this approach at all? I presume that even in this case, dbus would need to know that the GUI application is ok to listen to dbus-signals?
Are the different requirements in these two approaches expected? Or is it an inconsistency with dbus-python?
On Tue, Dec 25, 2012 at 10:04 PM, Ajay Garg ajaygargnsit@gmail.com wrote:
Thanks Simon.
Thanks for the extensive info; however it needs some hours (if not days :P) to be digested.
On Tue, Dec 25, 2012 at 9:24 PM, Simon McVittie < simon.mcvittie@collabora.co.uk> wrote:
On 24/12/12 08:26, Ajay Garg wrote:
For a recap of the brief history, I have a parent process, that is spawning a child process via "subprocess". Currently, the child-process is a GUI process; however, I intend to "behave" it as a dbus-service as well.
In general that is something that can work, but it's necessary to understand a bit about how main loops work, and how the modules of your process deal with a main loop.
Just saying "GUI" is not very informative: there are dozens of GUI frameworks that you might be using, each with their own requirements and oddities. If you say Gtk, or Qt, or Tk, or Windows MFC, or whatever specific GUI framework you're using, then it becomes possible to say something concrete about your situation.
Based on later mails in the thread you seem to be using Gtk.
I should note here that you seem to be using PyGtk (the "traditional" Gtk 2 Python binding), which is deprecated. The modern version is to use PyGI, the Python GObject-Introspection binding, and Gtk 3.
When using PyGI, you have a choice of two D-Bus implementations: either GDBus (part of gi.repository.GIO), or dbus-python ("import dbus"). I would recommend GDBus, since dbus-python is constrained by backwards compatibility with some flawed design decisions.
However, assuming you're stuck with dbus-python:
I then used composition, wherein another class, "RemoteListener" deriving from "dbus.service.Object" was made an attribute of the "main" class. That worked. However, when I do
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) GObject.mainloop.run()
in the "RemoteListener"'s __init__ method, the GUI of the "main" class fails to load (apparently, because the "mainloop.run()" causes the singular, main-thread to go into busy-wait).
Almost; it's not a busy-wait. GObject.mainloop.run() is the equivalent of this pseudocode:
def run(self): while not global_default_main_context.someone_has_called_quit: if global_default_main_context.has_more_events(): global_default_main_context.process_next_event() else: global_default_main_context.wait_for_an_event()
so it will loop until someone calls GObject.mainloop.quit() or equivalent, or forever if that never happens - but as long as nothing "interesting" happens, it will block on a poll() or select() syscall in what my pseudocode calls wait_for_an_event(), which is the right thing to do in event-driven programming like GLib/Gtk.
(If you replace the last line of my pseudocode with "continue", that would be a busy-wait.)
I tried option b), but now instantiating "RemoteListener" in a separate thread
It is unclear whether the dbus-glib main loop glue (as set up by DBusGMainLoop) is thread-safe or not. The safest assumption is always "if you don't know whether foo is thread-safe, it probably isn't". In any case, if it *is* thread-safe, the subset of it that's exposed through dbus-python isn't enough to use it in multiple threads.
GDBus, as made available via PyGI (specifically, gi.repository.GIO), is known to be thread-safe.
Is there a way to run GUI and a dbus-service together?
The general answer: only if either the GUI and the D-Bus code run in different threads, or if they run in the same thread and can be made to share a main context.
The specific answer for Gtk: yes, they can easily share a main context.
This:
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
sets up dbus-python's mainloop integration to integrate with the global default main-context in GLib (implementation detail: it currently uses dbus-glib to do that). What that means is that whenever a D-Bus connection started by dbus-python wants to listen for events on a socket, or wait for a timeout, it will ask GLib to add those to the global default main context as event sources.
This:
GObject.mainloop.run()
iterates GLib's global default main context, analogous to the pseudocode I mentioned before. Any "interesting" events that happen will cause your code to be executed.
A typical GUI application also needs to run the main loop to wait for events. In PyGtk, you'd typically do that with:
Gtk.main()
Gtk also uses GLib's global default main context, so this is pretty similar to GObject.mainloop.run() - if you just remove the call to GObject.mainloop.run() and use Gtk.main() instead, everything should be fine.
As per http://www.pygtk.org/pygtk2reference/class- gobjectmainloop.html, it seems that we must be able to add event sources to gobject.mainloop
Yes. For instance, gobject.timeout_add(), gobject.idle_add() and gobject.io_add_watch() all add event sources to the default main context.
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) tells dbus-python that when it needs to add an event source to "the" main loop, it should use equivalent C functions in GLib to do so.
(In principle, DBusGMainLoop ought to take a GObject.MainContext as an optional argument - but that's never been implemented, and it currently always uses the default main context, which is the same one Gtk uses, and which should only be iterated from the main thread.)
Once the event sources are added, each instance of gobject.mainloop (in its particular thread), will cater to only those sources.
No, that's not true; gobject.mainloop is a namespace for a set of global functions, not an object. If you must use multiple threads (not recommended), please see the GLib C API documentation for details of how main loops and main contexts relate, then the PyGtk documentation to see how that translates into Python.
How is dbus."mainloop.glib.DBusGMainLoop(set_as_default=True)" related to gobject.mainloop?
It instantiates a new DBusGMainLoop and sets it as dbus-python's global default main-loop-integration object. (With hindsight, DBusGMainLoop was a poor choice of name - it should have been DBusGMainIntegration or something.) The result is that whenever a new dbus.connection.Connection is instantiated, it will call methods on that DBusGMainLoop to connect its event sources up to the default GLib main context, which is the same one used by Gtk.
dbus.bus.BusConnection, dbus.Bus, dbus.SessionBus etc. are dbus.connection.Connection subclasses, so anything I say about dbus.connection.Connection applies equally to them.
How is dbus."mainloop.glib.DBusGMainLoop(set_as_default=False)" related to gobject.mainloop?
It instantiates a new DBusGMainLoop and doesn't use it for anything. If you save the returned DBusGMainLoop in a variable (e.g. my_dbus_g_main_loop = DBusGMainLoop(...)), then you can pass a keyword argument mainloop=my_dbus_g_main_loop to a dbus.connection.Connection constructor, and that dbus.connection.Connection will use that DBusGMainLoop instead of dbus-python's global default. In practice, only a very unusual application would need to do that.
There is currently no point in having more than one DBusGMainLoop; it would become useful if dbus-glib was thread-safe, and if dbus-python supported non-default GLib main-contexts.
Is it necessary at all to specify "mainloop.glib.DBusGMainLoop(set_as_default=True)" or "mainloop.glib.DBusGMainLoop(set_as_default=False)" when using gobject.mainloop?
Yes. Otherwise, dbus-python has no way to know that your application is going to be iterating the GLib main loop, as opposed to Qt or Tk or Enlightenment or something.
currently for the client, I am having the (client) (parent) process run the command "dbus-send" via the python-subprocess API. Does there exist a python API to do it in a cleaner manner?
Yes, either dbus-python or GDBus. Each of those can do everything dbus-send can, and more.
For a start, could you please point me to the paradigm to send a dbus-signal from the client to the server (where the server has the "add_to_signal_receiver" been set up).
From the limited googling that I did, I remember someone saying that for sending a signal, the typical setting-up-of-a-proxy-object is not required; however, I could not hit upon the exact dbus-python mechanism to send a signal :-\
S
dbus mailing list dbus@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dbus
-- Regards, Ajay
participants (1)
-
Ajay Garg