Background thread - entension code - switch contexts

Hi,

I am using wxRuby 1.9.7 and Ruby 1.8.6. I have built an extension in C++
and used swig to load it into Ruby interpreter, which works great. The
extension is an often long executing algorithm, and I’ve noticed that if
I fork a new Ruby thread in button clicked event, and in this thread run
the algorithm the application freezes for the time of execution. I have
looked through the archive and found that in the wxRuby main thread I
need to use timer, but that does not work for the “algorithm thread”, I
dont know how to stop it or keep GUI responsive? Can anyone suggest what
should I do. I know about Green Threads, and possibly that interpreter
can not switch context when extension is running, so what should I do
maybe use a thread at the side of extension (in c++ code) or maybe try
to use the same mechanizm with timer as wxRuby does (but how to
implement that).

My code is simple:

require ‘wx’
require ‘cppapp’ #myextension

class MFrame < Wx::Frame
STEPS = 100
def initialize
super(nil,-1,“Title”)
set_client_size(Wx::Size.new(300,300))
panel = Wx::Panel.new(self)
sizer = Wx::BoxSizer.new(Wx::VERTICAL)

btn = Wx::Button.new(panel, -1, "Click me")
sizer.add(btn, 0, Wx::GROW|Wx::ALL,2)

evt_button(btn.get_id){

    Thread.abort_on_exception = true
    #that does not work
    @th = Thread.new do
      Wx::Timer.every(20) do
        Thread.pass
      end
      method1
    end

}
panel.set_sizer(sizer)
sizer.fit(panel)

end

def method1
alg = Cppapp::Algorithm.new
alg.form = Cppapp::Form.new
alg.algorithmize
end
end

class MApp < Wx::App
def on_init
MFrame.new.show
Wx::Timer.every(20) {Thread.pass}
end
end
MApp.new.main_loop

Hey Lukasz,

I looked over the code, and it looks good. Now you said you developed
the
C++ Extension yourself, so that gives you a bit more control then most.
The
problem is, Ruby’s threads are green, and if you use an extension that
doesn’t recognize that Ruby is Green Threaded, and yield control back to
it,
it’ll effectivly do as you are seeing, freezing. With the examples
we’ve
given, we’re using Ruby’s core code, or using Sockets, with key point
select() calls. Ruby’s core code, and select() are specifically
programmed
to allow for Green Threads to be used. When you use select() in Ruby,
it’s
not a “true” call to the os level select. It’s timed to be used within
Ruby’s Threads itself.

What you will need to do, is make your C++ Extension aware of Ruby
Threads.
You will need to look at how to add rb_thread_yield() to your swig’ed
code,
to allow when an interation through your algorithim is completed, it
will
call rb_thread_yield(), to allow a pass from the C/C++ Code, back to
Ruby
code, so that it can do it’s iteration/processing, before giving control
back to your C/C++ Extension. That, unfortunatly, is the only way you
will
be able to improve upon the problem you are facing.

Hope this helps,

Mario S.

On Thu, Jun 12, 2008 at 3:00 AM, Łukasz ŁapiÅ„ski [email protected]

Mario S. wrote:

You will need to look at how to add rb_thread_yield() to your swig’ed
code,
to allow when an interation through your algorithim is completed, it
will
call rb_thread_yield(), to allow a pass from the C/C++ Code, back to
Ruby
code, so that it can do it’s iteration/processing, before giving control
back to your C/C++ Extension. That, unfortunatly, is the only way you
will
be able to improve upon the problem you are facing.

Hope this helps,

Mario S.

Thank you, I believe that is the only and good direction :). Now I will
investigate that.

If I could use some tips of yours :slight_smile:

I think I should place it somewhere here (the algorithm function)

SWIGINTERN VALUE
_wrap_Algorithm_algorithmize(int argc, VALUE *argv, VALUE self) {

Algorithm *arg1 = (Algorithm *) 0 ;
void *argp1 = 0 ;
int res1 = 0 ;

if ((argc < 0) || (argc > 0)) {
rb_raise(rb_eArgError, “wrong # of arguments(%d for 0)”,argc);
SWIG_fail;
}
res1 = SWIG_ConvertPtr(self, &argp1,SWIGTYPE_p_Algorithm, 0 | 0 );
if (!SWIG_IsOK(res1)) {
SWIG_exception_fail(SWIG_ArgError(res1), Ruby_Format_TypeError( “”,
“Algorithm *”,“algorithmize”, 1, self ));
}
arg1 = reinterpret_cast< Algorithm * >(argp1);

#HERE CREATE A THREAD FROM algorithmize function?

(arg1)->algorithmize();
return Qnil;
fail:
return Qnil;
}

I still don’t know where to get the ruby thread that I should pass to

rb_thread_yield(VALUE arg, rb_thread_t th)

Should I create it or get current ruby thread in the place marked above
in the code and then pass it to the algorithmize(rb_thread_t th) and
then in this function

void Algorithm::algorithmize(rb_thread_t th) {

time_t seconds;

seconds = time (NULL);
time_t tmp = 0;
int nb = 0;

//iterations
while((tmp = time(NULL) - seconds) < 4)
{
if(tmp > nb)
{
nb++;
#THIS GOES IN THE ITERATION
rb_thread_yield(some_VALUE, rh);
}
}
}

Kind regards,
Łukasz Łapiński

Hi,

thanks for help,

Finally I have used

void rb_thread_schedule()

function which is not static and I can declare it to be extern in my C++
extension, what I did is

extern “C”
{
extern void
rb_thread_schedule();
}

using namespace std;

void Algorithm::algorithmize() {

time_t seconds;

seconds = time (NULL);
time_t tmp = 0;
float nb = 0;

//iterations
while((tmp = time(NULL) - seconds) < 5)
{
if(tmp > nb)
{
cout<<nb<<endl;
nb = nb + 0.01;
rb_thread_schedule();
}
}
}

And it works.

Thanks,
Łukasz Łapiński

Hello again Lukasz,

On Thu, Jun 12, 2008 at 11:52 AM, Łukasz ŁapiÅ„ski [email protected]
wrote:

Thank you, I believe that is the only and good direction :). Now I will
investigate that.

No problem.

If I could use some tips of yours :slight_smile:

I think I should place it somewhere here (the algorithm function)

I’m sorry, I meant the use of rb_thread_pass(), instead of
rb_thread_yield(). rb_thread_yield() is pretty much like the internal
rb_yield(), which yields control back to Ruby, with some variable
passed.
Generally meant for Blocks. Where as rb_thread_pass() is meant to pass
control back to the Ruby Thread Scheduler, and on to the next thread in
the
list of threads.

Should I create it or get current ruby thread in the place marked above
in the code and then pass it to the algorithmize(rb_thread_t th) and
then in this function

You are partly right, again rb_thread_yield() is not the method to go
with,
my apologizes, but your idea for Algorithim::algorithmize() would be the
best solution, only replace rb_thread_yield() with rb_thread_pass().
The
main thing you need to look at, is allowing Ruby’s Thread Scheduler back
control, so that it can give processing time to other Ruby Threads. If
you
don’t do that, then all other threads will freeze till your C/C++
routine is
completed. And that is not what you want.

{
if(tmp > nb)
{
nb++;
#THIS GOES IN THE ITERATION
rb_thread_yield(some_VALUE, rh);
}
}
}

Good luck,
Mario S.