outside-dispatch-lock handlers
Thiago Macieira
thiago.macieira at trolltech.com
Thu Mar 2 01:57:27 PST 2006
[Trying to keep my reply to a minimum size...]
Havoc Pennington wrote:
>Let's define "processing the handler queue" as:
> - for each handler in the queue that applies to the same
> message, run the handler
> - if a handler returns NEED_MEMORY, put it back in
> the queue and return from dbus_connection_dispatch()
> (this is how you can get handlers for multiple distinct
> messages in the queue at the same time)
> - if a handler returns HANDLED, drop the rest of the queue
> that applies to the same message, and return
> - if a handler returns NOT_YET_HANDLED, remove the handler
> and run the next handler
>
>The overall dbus_connection_dispatch() algorithm then is:
> - if the handler queue is not empty, process the handler
> queue and return. This runs handlers for at most one
> message, possibly leaving handlers for more messages
> in the queue.
> - if the handler queue is empty, and the message queue
> is not empty, borrow the next message (take dispatch lock)
> - add all handlers for that message to the handler queue;
> if we run out of memory here, remove handlers and
> return the message and return from dispatch
> (i.e. fail atomically)
> - once we've queued all handlers, steal the message
> (drop dispatch lock)
> - process the handler queue as defined earlier
IIUC, this means "process the handler queue without a dispatch lock". Is
that so? That sounds great for me. :-)
>OK so say we have signals A, B, C, with handlers:
>
>A: 1, 2, 3
>B: 4, 5, 6
>C: 7, 8, 9
>
>while dispatching A, inside handler 2 we dispatch again twice; we'd
>first run handler 3, and then queue and run 4, 5, 6.
I'd rather 3 weren't run until 2 finished running.
>But the problem is that we don't know whether we should run 3 until we
>know the return value of 2. i.e. right now there's a requirement that we
>always _complete_ each handler before we can run the next.
Exactly.
>The "outside handler" idea essentially splits each handler into two
>halves:
> - the "will I handle this?" half
> - the "actually handle this" half
>
>If we did that, we could run the "will I handle this?" half when we
>build the handler queue, and the "actually handle this" half could be
>run when we process the handler queue. This means that in the above
>example, we would know whether 3 should be run or not, before 2 has
>returned.
"we would know whether the 'actually handle 3' would be run
before 'actually handle 2' has returned" <-- is that what you meant?
>One way to think of this is that we're keeping all the state of
>dbus_connection_dispatch() in the DBusConnection instead of local
>variables. Whenever you call dbus_connection_dispatch(), it just
>"resumes" where it was last.
Hmm... not sure I agree. I've got to think a bit more about the
consequences, but I'd rather dispatching were "atomic". I.e., either
dispatch() does it fully or it does not. And recursively calling
dispatch() won't resume an earlier dispatch -- it can only start a new
one.
>I think the effect of all this is that when a handler recurses, it
>"jumps itself ahead" in the handler queue. i.e. if I'm handler 2, when I
>recurse any number of the other handlers 3-9 could be called.
Yes. If you recurse, you *want* other handlers to be called.
For instance, you could recurse in order to wait for a signal that you
know is coming. (Hal sends 3 signals PropertyChanged if you try to lock a
device)
>When the
>recursive dispatch returns, then handler 2 would be running _after_
>handler 9. However, a handler does not jump _other_ handlers out of
>order - handler 3 still runs before 4.
I wouldn't say "after 9", but "handler 2 is running _around_ handlers
4-9". I still think handler 3 should be run after 2 has finished, since
it's processing the same message.
>Say you are crazy and now you start calling dispatch() from several
>threads at once... first theory is "don't do that then" but say we
>wanted to invent something 'sane' to happen, my best suggestion is:
> - we hold a recursive lock when invoking a handler
> - that handler can recurse normally, other threads wait
>this preserves ordering of handlers and still allows recursive dispatch.
Agreed. Should this be done in libdbus or in the binding? If in libdbus,
we need to add a new lock and add recursive mutex behaviour.
But going back to your A, B, C & 1-9 example, if thread α called
dispatch() to handle message A and, before it finished, thread β called
dispatch() too, why not let it handle message B?
Of course, if the binding code is doing that, ALL handlers must be not
only reentrant, they must be thread-safe. Which again poses the question
of cooperation between bindings: are everyone's handlers thread-safe?
>I think this is normally a lot safer and nicer for apps than running all
>the intermediate handlers up to the message reply, if they just want to
>block for the message reply. But if you want to also process incoming
>method calls etc. while blocking you would just avoid ever using
>send_with_reply_and_block() from your current thread, only use
>dispatch() or pop().
That's what I'm doing.
>> Which brings us back to the problem of self-thread recursing. If the
>> user code recurses and wants to process more messages, there can only
>> be two possible outcomes: either we find the specific message that the
>> user wants to process among the many messages we're receiving, or we
>> process them all.
>>
>> I chose solution #2 for the Qt bindings for the moment. DCOP's
>> solution is #1.
>
>If I understand you correctly, my opinion is that solution #2 is right
>for method calls and signals, and solution #1 is right for method
>replies. Note that send_with_reply_and_block()/DBusPendingCall implement
>solution #1 for method replies. Solution #2 I'm theorizing should work
>as I just described above (the "handler queue" model)
The difference here is the concept of "find the specific message the user
wants". DCOP understands that a method call coming from the same app that
you called while it's processing your call is something you want to
handle. More so, if you're calling app A, A called B during the
processing of your call, and B called you back while processing A's call,
DCOP also understands that you need to process the call.
Extend that to signals as well in D-Bus.
>The wait condition is for an IO lock on the socket, which only needs to
>be acquired to read/write from the socket. So it takes the IO lock in
>order to then block on the socket. If several threads try to
>send_with_reply_and_block, then one will block on the socket and the
>others will block on the IO lock until they get woken up. The dispatch()
>thread also participates in this, so whenever it needs to block it can
>block either on the socket or on the IO lock. All these threads will be
>queuing up messages as they read from the socket, but only the dispatch
>thread will drain the queue - the send_with_reply_and_block threads will
>just pull out the exact message they are waiting for, and otherwise
>ignore everything.
Is there any possibility that a dispatch() running at the same time as
send_with_reply_and_block steal the message the block is waiting for?
>Anyway, considering all this what concrete changes would be needed to
>implement the "handler queue" semantics:
> - we need each handler to have both "will I handle?" and
> "actually handle" operations (ideally not provided by the app
> programmer - bindings should be able to do these most of the time)
Indeed. I'd advise against calling any user code from the "will I handle?"
operation.
> - when calling the "will I handle?", recursion is not allowed/sensible,
> a non-recursive lock would be fine around these calls
> - when calling the "actually handle", recursion is allowed in the same
> thread, a recursive lock would be used around these calls
>
>We would have to work out the specific API changes to get the two
>operations for each handler, or some clever way to avoid these changes.
>
>The HANDLED_OUTSIDE_DISPATCH change effectively does add the two
>operations, though in a sort of strange way.
Yep. For a first draft, it was a good idea, but it needs refining.
>The current handler function becomes "will I handle?" and it returns
>HANDLED_OUTSIDE_DISPATCH if yes, and NOT_YET_HANDLED if no.
>If yes, then no further handlers are added to the handler queue for the
>message, if no then this handler is added but others may be also.
>This function can't recursively dispatch.
It currently can't, so there's no problem.
>A "legacy" handler not aware of the new setup will behave a bit oddly,
>since it will "actually handle" when asked "will you handle?" - this
>means it will be ordered incorrectly with respect to handlers in new
>code that get postponed until the handler queue is processed, and it
>means that recursion won't be allowed inside a "legacy" handler.
It already isn't allowed. No change here.
>If we ignored back compat, we could probably make this a lot less
>confusing. Something like:
>
> enum HandlerCheckResult {
> INVOKE_HANDLER,
> INVOKE_HANDLER_THEN_STOP_PROCESSING,
> DO_NOT_INVOKE_HANDLER,
> NEED_MEMORY
> }
We could do that by adding the new enum values as copies of the current
ones, then drop compat in a later version.
--
Thiago José Macieira - thiago.macieira AT trolltech.com
Trolltech AS - Sandakerveien 116, NO-0402 Oslo, Norway
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 191 bytes
Desc: not available
Url : http://lists.freedesktop.org/archives/dbus/attachments/20060302/8966f280/attachment-0001.pgp
More information about the dbus
mailing list