Properties interface additions

Simon McVittie simon.mcvittie at collabora.co.uk
Tue Jan 27 03:41:11 PST 2009


On Wed, 14 Jan 2009 at 14:50:13 -0500, David Zeuthen wrote:
> to indicate property changes. Of course this only works if the other end
> is using EggDBus but for the projects I'm working on this happens the
> case. Still, I'd like to propose that we add a Changed() signal to the
> D-Bus specification. I've attached a patch for this. OK to commit?

I have several objections to this signal.

The killer problem with adding signals to existing interfaces,
particularly ones as core as DBus.Properties, is: how do you distinguish
between a service that will never emit the signal because it's too old,
and a service that can be relied on to emit the signal? I tend to think
this is sufficient to reject this change.

Unconditionally emitting a signal with the new value for all properties could
also cause performance problems, depending what sort of thing you use
properties for. On the other hand, *not* emitting the new value triples
the number of D-Bus messages needed (signal + request + return) and
opens up unpleasant races (you might never be able to find out
the intermediate value if two changes happen consecutively).

In Telepathy, we've been migrating from getter methods to readable properties,
in order to be able to use GetAll() to reduce round-trips. Among other things,
the list of all the members of a chatroom (which is quite large if you're in
#ubuntu!) is a property. We do change notification for the members list
via a MembersChanged signal that lists the members who joined, and the
members who left (diffs rather than the whole new value), which makes
the sizes of the signals a lot more manageable - the initial state
download is still large, but it's unicast rather than broadcast.

Using a generic PropertiesChanged signal also removes the possibility to
include information relating to the change, like the reason why the
change occurred.

In Telepathy we decide on a case-by-case basis between these two
patterns (imagine we're exporting an interface MyInterface with properties
Foo and Bar):

* signal MyInterface.FooChanged, signal MyInterface.BarChanged
* signal MyInterface.PropertiesChanged(a{sv}: property => value)

This means that bindings that connect to signals per interface
(dbus-glib) and bindings that connect to signals per signal
(dbus-python, QtDBus) don't get woken up for signals on extension
interfaces that they don't understand anyway ("per interface" seems like
a good granularity for whether a client does or doesn't care).

In the FooChanged/BarChanged case, you can choose whether to represent
the change as the new value, the old and new values, a diff, or just an
indication that the client should poll the value again. For instance, group
membership changes are signalled as diffs as I explained above; changes
to most details in the account manager are signalled with the whole new
value (they're fairly small), but the user's avatar in the account manager
is signalled as a change notification with no arguments, on the assumption
that not many clients will actually be interested in the (potentially large)
avatar data, and those that are interested can call Get() for it.

You can also include extra information, and make changes to several
properties (appear to be) atomic: for instance, our Group.MembersChanged
signal actually indicates changes to the members, the set of "local
pending" members (those waiting for the local user's approval), and the
set of "remote pending" members (those waiting for network activity to
happen) simultaneously, and also includes an indication of who caused
the change, why the change occurred, and possibly a message (e.g. in an
IRC channel these might be "mr_chanop", KICKED, "stop spamming").

As a result, we can correctly represent a user moving from one state
(set) to another, e.g. the move from remote-pending to member that
happens when an invited user joins a chatroom: we emit a single
MembersChanged signal removing the user from the remote-pending set and
adding them to the members set, for reason INVITED.

So, in summary:
1. adding this signal would make existing bindings and services instantly buggy
2. the signal can't be relied on, because of 1.
3. clients ignoring 2. and using this signal anyway will be woken up
   unnecessarily when using extensible services
4. signals added by the designers of individual interfaces can be
   considerably better for their particular situation than this generic signal

> I've also thought about adding these two methods
> 
>  o SetMultiple(IN STRING interface_name, IN DICT:STRING->VARIANT props)

This seems plausible; client code could fall back to repeated Set calls
if this failed (although that'd lose atomicity). On the other hand,
there's nothing to stop you having a multiple setter in your interface
already; in Telepathy we'd generally have a method
SetStatus(s: status, s: message) or whatever, which is easier to document.

>  o GetAllInterfaces(OUT ARRAY:(STRING, DICT:STRING->VARIANT props))

This is something we've thought about adding for Telepathy, for
(further) round-trip reduction. Client code could fall back to
calling GetAll once for each interface that it cares about, so this isn't
incompatible. Why does it return an a(sa{sv}) instead of an a{sa{sv}}, though?

Depending what you're optimizing for,
GetManyInterfaces (as: interfaces) -> a{sa{sv}} would be better if you
want to reduce message size (assuming that each client cares about a
finite number of interfaces) but worse if you're optimizing for simplicity.
Telepathy's GetContactAttributes method has a similar design.

Regards,
    Simon


More information about the dbus mailing list