Hi Adam!
On Monday, 5 December 2011 at 11:23, Adam Bukowski wrote:
Problem is that when I call ruby handler from different thread it causes
cfp consistency error and rest of code in ruby file isn’t working
properly. It hangs waiting for window-change. When window is changed
then it goes to next instruction.
Yes. You cannot call Ruby functions from a thread not created by Ruby.
When
it comes to MRI, you must be the owner of the GIL to call ruby
functions, and
since the thread is not a ruby-created thread you have no way of owning
the GIL.
You can work around this, but it is not easy. But before you try
working around
it with the solution I am about to (try to) explain, I urge you to read
the later parts
of my post about rb_thread_blocking_region
.
Anyway,
Dealing with this kind of asynchronous callback in Ruby means you will
need
a thread in Ruby (either main thread, or just another thread, we call it
the callback
thread) waiting for the callback to happen. This waiting needs to be
implemented
in C, as you will need to convert all the data you need from the
callback to Ruby
VALUEs from C values.
Because the call_handler
is in a non-ruby thread, it cannot call ruby
functions.
So, to pass information when this handler is called you need to have
something
that can call Ruby functions wait for the call_handler
to be called,
and then
grab data from call_handler
, convert it to Ruby values and then use it
however
you want.
The only way I know of doing this is by setting up a global condition
variable that,
from a Ruby-created thread, you wait for signals on (make sure you use
the rb_- thread_blocking_region
function or you will block all of Ruby). When
call_handler
is called you put the title in a memory location accessible from both
the waiting C
function from Ruby, and call_handler
itself.
call_handler
will then put the raw title (or any kind of information)
in this shared
memory location and then signal the Ruby C waiting-function that the
callback has
been called. Once it has signaled, this call_handler
can exit. Just
make sure that
you don’t lose the parameters from memory when you exit the
call_handler
.
Anyhow, now when Ruby C waiting function has been signaled it knows that
it has
some data. It will take this data from the previously mentioned shared
memory loc-
ation and convert it to Ruby VALUEs that you can use in your Ruby code.
I’m sorry if this seems confusing. I’ve tried explaining this before by
writing a blog
post about it:
http://burgestrand.se/articles/asynchronous-callbacks-in-ruby-c-extensions.html
(keep in mind, the code might not work on windows, it is only for
explanation of concept)
If you need another implementation, you can also look at Ruby FFI, that
does a very
similar thing to handle callbacks:
https://github.com/ffi/ffi/blob/master/ext/ffi_c/Function.c
(look for words gvl, cb, async; do keep in mind Ruby FFI’s solution
works on windows,
ruby 1.8.7 and is a more general solution than what I have in my blog
post)
I’ve tried not to use native thread and call this blocking wnck’s loop
in ruby’s Thread.new, but it hangs totally then.
If you try a blocking operation in any Ruby thread it will not allow
other threads to run.
This, again, is because of the GIL. If you need to do a blocking
operation in C, you can
use rb_thread_blocking_region
to do it:
https://github.com/ruby/ruby/blob/ruby_1_9_3/thread.c#L1079
This method will first unlock the GIL, allowing other Ruby threads to
run, and then call
your blocking operation (supplied as first parameter). If you need to
pass additional data
you can use the second parameter, which will be given to the blocking
function as its’
argument.
If Ruby needs to abort the blocking function (any reason, for example if
user tries to exit
your script) it will call the unblocking function given as third
parameter, with the fourth
parameter as its’ argument.
I wish you the best of luck, Adam!
PS: You might find rb_thread_call_with_gvl
when you look through the
source, and it
might look promising. Problem with this function is that it is useless
when you are in a
non-Ruby thread. It is only useful if you are within a function that you
came into by using
rb_thread_blocking_region
, to temporarily jump back into Ruby and do
some operation.
— Kim Burgestrand