glib dbus bindings notes

Havoc Pennington hp at pobox.com
Tue Feb 24 21:16:26 PST 2009


Hi,

On Tue, Feb 24, 2009 at 9:03 PM, Havoc Pennington <hp at pobox.com> wrote:
> eggdbusnametracker

Another thing I just noticed in here, it looks like when the initial
GetNameOwner returns then you aren't emitting the
bus-name-gained-owner signal. I'd suggest that bus-name-gained-owner
reflect state changes in the *client* knowledge of the owner, not
changes in the bus state. When the first GetNameOwner reply arrives,
you could emit this.

One of the ways the binding can help people write solid async code,
instead of driving them to give up and use sync APIs, is strong
invariants. An important one is that when you watch a bus name, it's
guaranteed that if the name exists your "bus name appeared" callback
will be invoked; and that it's guaranteed the appeared/disappeared
callbacks alternate.

This allows you to write:

DBus.session.watch_name("org.gnome.Foo",
                                            function (owner) {
                                              createProxiesAndStuff(owner);
                                            },
                                            function(oldOwner) {
                                               proxies = null;
                                            });

Rather than:

owner = get_name_owner("org.gnome.Foo"); // blocking, yuck
if (owner)
   createProxiesAndStuff(owner);
DBus.session.watch_name("org.gnome.Foo"),
                                            function (owner) {
                                              if (!owner)
                                                createProxiesAndStuff();
                                            }
                                           },
                                           function (oldOwner) {
                                           });

Requiring the synchronous call in front just adds code; if the async
API guarantees it will be called, then there's no need for the
synchronous init on startup; init is the same as handling of restart.
It's the same principle as my suggested unique application API, which
has a mostly empty main() and the usual contents of main() go only in
the "got unique instance" callback.

The same is true of the other async APIs I suggested. The key is the
invariant that the callbacks will be called, and that they always
alternate, so apps can just put setup and teardown code in those
callbacks unconditionally and not have any separate first-time
initialization code.

With the acquire_name (monitor name ownership) callbacks, if you
guarantee that *either* the "got name" or "lost name" callback will be
called, and that they will then alternate, there's no need to
separately handle never getting the name vs. losing the name at some
later point.

With strong invariants the only difference from doing things
synchronously should be wrapping the init code in a callback, rather
than putting it in main(). Other than those extra 4 or 5 lines,
there's no penalty to doing the whole initially-create-a-proxy song
and dance asynchronously.

You could even auto-create / auto-destroy the proxy, like:

DBus.session.watch_name_creating_proxy("org.gnome.Foo",

function (proxy) {

 // proxy arrives here with object properties already asynchronously

 // filled in and unique name owner known
                                                                     });

which is just as easy as the blocking code.

Anyway a lot of this thorny stuff can be done for people and gotten
right in a shared layer used by all bindings. A key win is to be sure
people *only* have to write callbacks, never 1-time initialization
code. Callbacks / change monitoring only feel like a burden when they
are *extra* code, if they are the *only* code then all they're adding
is another level of  indentation, and they actually get tested since
they run on normal initialization.

Havoc


More information about the dbus mailing list