[Dbus-cxx-devel] Race hack fix
Status: Beta
Brought to you by:
rvinyard
|
From: Patrick A. <pa...@ph...> - 2010-03-03 22:49:56
|
Hi all:
The watch/dispatch race fix in dbus-cxx 0.6 still needs a bit more
tweaking, unfortunately. The current fix does: after a watch is handled,
it sets need_initiate_processing_hack, and then if that's set, it wakes
up the dispatch thread.
That's not exactly the problem, though - the dispatch thread will just
wake up, see that it has nothing to do (since the dispatch status is
complete) and go back to sleep. In fact, to remind, the problem is:
1) watch thread wakes up, select() says there's data to read
2) calls handle_read() on that Watch
3) This queues a message, which changes the dispatch status, which wakes
the dispatch thread.
4) The dispatch thread dispatches the message, which calls
Connection::send for the reply. The watch thread still holds the IO
path, so this message gets silently queued
5) The watch thread finishes handle_read().
In the current hack, all that happens next is that
6) the watch thread wakes up the dispatch thread again
7) the dispatch thread sees the dispatch status is DISPATCH_COMPLETE and
wonders why it was woken up before going back to sleep.
There are two ways to actually fix this (well, 3 really):
1) Set a mutex between the watch thread and the dispatch thread such
that you never dispatch while handling watches, and you never handle
watches while dispatching. For some reason I couldn't get this to work.
It should be simple: just a mutex lock around each if (FD_ISSET()) {}
block, and a mutex lock around the (*ci)->dispatch() calls. I probably
just screwed it up simply somehow.
2a) At the end of the watch thread, loop over the connections, and if
there are messages pending, call Connection::flush on them. This is
sadly a bit brute force because there could be messages pending due to
the socket being full, in which case the thread will block
unnecessarily. It's also a pain because the watch thread doesn't loop
over connections at all, so it's not that elegant.
2b) Change DBus::Connection::signal_add_watch to take a Connection
object as well as the watch object, and add a DBus::Connection pointer
inside the Watch. Then, after the watch thread completes, loop over all
watches in m_write_watches, and then if
watch->connection()->has_messages_to_send && !watch->is_enabled() , call
watch->connection()->flush(). You could do this with no changes at all
currently, if you matched the watch->unix_fd() to the
connection->unix_fd() but that seems complex. Not sure it's as simple as
this, though, because the question of ownership of the DBus::Connection
object exists, so maybe matching via the unix_fd() etc. is needed (geh).
Patrick
|