C convenience lib (was Re: Is dbus_pending_call_set_notify() thread safe?)
Havoc Pennington
hp at redhat.com
Thu Aug 2 14:52:30 PDT 2007
Hi,
Thiago Macieira wrote:
> Nothing. I've just realised that there's a check to make sure it's no-op.
> I hadn't seen it before, which made me think that calling it more than
> once was dangerous.
I think you were right about this at some point in the past btw, it just
got fixed at some point and the de facto situation is everyone just
calls dbus_threads_init_default(). In theory if nothing in your app
needs threads, they are never initialized and libdbus is mildly more
efficient. (Probably saving 5% overhead or something.)
> So, if you're writing a mainloop-specific code, you must assure that the
> binding is loaded into memory and is working. If you know that is the
> case, then why not use the binding already?
>
> Therefore, I argue that the only valid reason for using libdbus-1/wrappers
> directly, instead of a binding, is to write mainloop-agnostic code. And
> since you must not assume anything about mainloops, you must handle your
> connection's dispatching yourself. That means dbus_bus_get_private and a
> private thread for handling asynchronous events.
Hmm. OK let me break down some multiple issues again ;-)
1)
So I think it would be OK for a library or plugin to assume "something
will dispatch the session bus connection" and that would work fine, with
no direct dependency on say dbus-glib. All a lib/plugin really has to
know is that something will be doing the read/write/dispatch task.
Contradicting my previous mail a bit, conceivably this allows some
simple plugins or libraries to avoid caring about the main loop, though
it would only work if the plugin never needed a main loop for any
_other_ dbus-unrelated reason.
2)
Something of a special case is when a library or plugin is a dbus object
system mapping.
There's some split I think between a mainloop/dispatcher, and an object
system mapping.
For example, in the Mugshot client app, we use a lame object system
mapping I came up with ad hoc. However, we use the mainloop/dispatcher
from dbus-glib, and we have libraries in the same process which I think
use dbus-glib's object system mapping.
As another example, you can use the glib mainloop/dispatcher with the
Python object system mapping, for example in a PyGTK application. [1]
The object system mapping just assumes "something will do
read/write/dispatch" and that's all it has to know.
This dispatcher vs object system mapping distinction does not change
your fundamental point, but it does mean the thing that has to be
globally decided on a shared connection is smaller and less consequential.
It sounds like this doesn't work in QtDBus since the Qt object system
mapping assumes the Qt dispatcher is used, but AFAIK the GLib and Python
bindings for example don't make assumptions about the dispatcher (other
than "it exists")
Anyway, to simplify slightly (since there are actually N shared
connections[2]), the question here is whether the conceptual global
variable:
DBusConnection *the_session_bus;
is in libdbus or in dbus-glib and QtDBus.
I would say that either way, conceptually this global variable "belongs"
to the single dispatcher in use for the given app.
There's some very small utility to reducing the code each dispatcher has
to write slightly, by having libdbus deal with the global variable,
thread locking it, and zero'ing it out on disconnect.
I think there's some utility to allowing say Python object system
mapping and GObject object system mapping to share a single connection
and single dispatcher.
If they don't share that, I don't think it's a tragedy; an object system
mapping that requires a specific dispatcher can use a private connection
and the only harm is a bit more overhead.
(Though I think people do underestimate how big DBusConnection is and
the potential network overhead of creating one, esp. if we make TCP work
well.)
3)
The shared connections are intended to be "claimed" by some dispatcher.
So it is fair game, IMO, for QtDBus to entirely take over the shared
connections and just say "if you want to be in-process with QtDBus you
have to use the shared connections as we set them up."
It sounds like a lib or plugin as I described in 1) above won't work
anyway, but QtDBus can still own the shared connections if it likes.
It would just be a policy of QtDBus that it forbids non-QtDBus usage of
shared connections.
That is effectively the policy anyway, since QtDBus is not dispatching
the shared connections, a plugin or lib as described in 1) won't work
anyhow. Given that this is already true in effect, there's no reason
QtDBus can't just take over the shared connection. There may be no
benefit to doing so, but there's also no reason not to.
We could add some API to essentially "assert" that only one dispatcher
claims the shared connections. That might serve some of the same
preventing-weird-bugs purpose as deprecating shared connections.
dbus_set_shared_connection_dispatcher(const char *name,
gboolean allow_other_dispatcher,
DBusConnectionSetupFunc func,
void *data);
or something like that.
If allow_other_dispatcher it means we don't care if someone else
dispatches as long as they call our registered callbacks (so e.g. a
plugin could set a dispatcher "just in case", like threads_init).
However QtDBus would have to do allow_other_dispatcher = false which
would mean we abort() if someone else had already taken over the shared
connections.
We could also have something like
const char* get_shared_connection_dispatcher()
which would allow object system mappings to do an
assert(I have the right dispatcher)
4)
The DBusConnection API _is_ designed for multiple object system mappings
to share the connection; that's the purpose of the "object tree" for
registering what object paths you want to handle, and the return values
from the callbacks like HANDLED vs. NOT_YET_HANDLED.
This has worked fine for me for object mappings like the dbus-glib
GObject one combined with my lame custom one, a combination I've used
successfully.
I do think it's a bug if it's impossible to coordinate multiple object
system mappings on one connection using the available APIs, though if we
deprecated shared connections it is obviously not a very important bug.
I'm tangentially interested in why QtDBus wants to own the message queue
and object tree rather than coordinate, if the reasons are likely to
apply to other libs or object system mappings using the connection.
5)
If we deprecated shared connections we could actually remove them
internally and always create new connections, since the shared getters
do return a reference count and make no real guarantees about sharing,
the API contract is just that the connection *could* be shared.
However, legitimate code people are using now could end up creating a
heck of a lot of new connections if we did this. It would work but be
crazy inefficient.
6)
The reason to deprecate as you say is "to avoid silly errors of
applications using the connections shared by libdbus-1"
Do we really have a problem here in practice, though? I would expect
that if someone did this their lib or plugin or whatever would just not
work and they'd notice pretty quick.
If this is biting people I'd like to address it somehow.
7)
This whole conversation may be relevant to people who are wanting to
have the binding own the message queue and the marshaling.
i.e. there was this discussion at GUADEC (see my notes from there) about
offering an API that just created the socket and did the auth protocol,
then returned a file descriptor.
Then allow bindings to parse and build messages, avoiding the
queue/object-tree in DBusConnection and avoiding the DBusMessage object.
Obviously this means that bindings would have to be both dispatcher and
object system mapping, and that shared connections would be unworkable.
If you think about it, basically the whole purpose of DBusConnection is
to share the connection - if we didn't want to share the connection, we
could just have a "connect and auth, return fd" and then possibly also
marshal/unmarshal helper API.
There are a couple other small DBusConnection purposes (e.g. in theory
we could have a non-descriptor-based transport like an X client message
tunnel), but they aren't too important. The main point of DBusConnection
is to coordinate multiple users of the connection socket.
This "just give me an fd" API is kind of hypothetical for now though
unless I or someone gets time to hack on it ;-)
8)
As I also said in that GUADEC mail, for C apps, I think apps are
suffering most from lack of high-level API to do things like
introspection, single-instance, tracking bus names, and matching
signals, so we probably should think about that in addition to tweaking
libdbus to death and/or reimplementing parts of it...
Havoc
[1]
(Though I think dbus-python didn't use the shared connection, I thought
the reason was something to do with refcount bugs, I'm not sure.)
[2] the shared bus connections (dbus_bus_get()) are essentially just
global variables and the implementation of dbus_bus_get() would not be
especially hard to redo in a binding. However, there's also a
not-really-ever-used-right-now-but-maybe-someday-useful-possibly sharing
of connections by server GUID, which has the theoretical purpose that if
you did a CORBA-looking usage of dbus, with "IORs" and no bus daemon,
you would want to avoid creating a new connection object for every
method call. Thus there's basically a hash from the server GUID (which
would be in the "IOR") to shared connections. Anyway, we can ignore this
case, but it does exist in the code for historical reasons.
More information about the dbus
mailing list