It seems like a common problem when using Ruby-GNOME2 (particularly
GTK+) is having other Ruby threads be able to signal back to the main
loop to run some GTK+ code or other stuff that needs to be done in the
main thread. Even Gtk.idle_add and the like seem to not be completely
safe with Ruby threads, because GTK+ doesn’t know about the Ruby
threads at all.
An obvious interface to have would be a way of pushing blocks to the
main loop; I see people in the mailing list archives doing this, but
either they do it with timeouts (which results in continual polling,
which is poor design) or… what? I don’t see people obviously doing
it any other way.
The first obvious way to wake up the main thread would be some kind of
plain interthread wakeup call, but there doesn’t seem to be a way to
do rb_thread_select and atomically release a mutex, so there’s no way
to reliably get the wakeup signal to the main thread that I can see.
So instead, it looks like a slightly better/worse functional
solution—which I think someone proposed earlier, but I’m not sure
when—is to use a pipe that automatically gets added into the select,
and write a byte to it whenever we push a new function for the main
loop to call.
I have attached an experimental diff against Ruby-GNOME2 SVN that adds
the function GLib.main_thread_do_async { … } that pushes Proc
objects to the main thread in what looks to be the proper thread-safe
way, and uses a pipe to wake it up.
Does this seem like a basically reasonable thing to add to
Ruby-GNOME2? (Not necessarily in the exact form attached, of course.)
Problems and questions:
-
It seems like it should be possible to do this in Ruby with
a GLib::IOChannel with a watch or something, but I tried this, and
it didn’t seem to work; it sometimes hung or crashed. I’m not sure
why. -
Maybe we can’t create the pipe on startup. Is it reasonable to
expect to be able to do this? -
This patch also removes the constant empty timeouts, to make sure
that the functionality still works without them. Really these
should go away too, if it is possible, but does this break
backwards compatibility? It seems that with the absence of a way
to get messages to the main thread, many extant Ruby-GNOME2 programs
will rely on the timeout kludge to keep the main loop iterating
constantly and just ignore the unsafe thread usage. -
The pipe write can block. This isn’t so bad, but it should probably
be avoided in an async function. This can be avoided by using a bit
more rb_thread_critical and only writing new bytes when the previous
ones have not been acknowledged yet, but I haven’t written this yet. -
Sometimes we may actually want the async pushes to block; if the
GLib
main loop gets hung somehow, the pushes will just fill the queue up
with procs forever. Maybe that’s a sign of an incorrect program
anyway, though, so it doesn’t matter.
A sample program which seems to basically work in the proper
thread-safe manner after this patch is applied is also attached.
What do people think of this?
—> Drake W.