Is it possible to avoid longjmp in exceptions, Thread#kill, exit(), signals?

Hi, my Ruby C extension runs a C loop (libuv) without GVL. At some
point if an exception, Thread#kill, exit or a non trapped signal
occurs, Ruby executes longjmp and leaves my C loop in an inconsisten
status (i.e. while it was executing an internal libvu C iteration or
whatever).

My Ruby code does an “ensure” and so, but in some corner cases I
cannot reuse the existing C loop, nor I can free its resources at C
level.

My question is: is there any way to avoid the longjmp? or a different
approach: is there any way from my C code to avoid being interrupted
while I go to Ruby land (by acquiring the GVL)?

Thanks a lot.

Please forget this for now. I’ve realized that my call to rb_protect()
didn’t rescue exceptions if they were not StandardError (due to a
check I did in my C code). So I do can rescue at C level any kind of
error/exception, inclouding Interrupt or Exit and act according.

So please let me work a bit on it and I will comment the result.

Thanks a lot.

2012/5/29 Iñaki Baz C. [email protected]:

Am 29.05.2012 20:37, schrieb Iñaki Baz C.:

Please forget this for now. I’ve realized that my call to rb_protect()
didn’t rescue exceptions if they were not StandardError (due to a
check I did in my C code). So I do can rescue at C level any kind of
error/exception, inclouding Interrupt or Exit and act according.

So please let me work a bit on it and I will comment the result.

Thanks a lot.

You seem to work quite heavy with Ruby’s extension mechanism and
experience a good number of corner cases and semi-hidden features (like
the GVL reaquiring thing). When you’re done with your libuv extension,
could you write a blogpost or something like that summing up what you
learned while doing this extension? I know, many things are online in
the ruby-talk archives, but it would be nice if the information could be
centralised at a single place. I’m sure many Ruby developers doing more
complex C extensions would be happy to have this.
Just a suggestion. If you don’t want to, ignore it.

Vale,
Marvin

2012/5/29 Quintus [email protected]:

You seem to work quite heavy with Ruby’s extension mechanism and
experience a good number of corner cases and semi-hidden features (like
the GVL reaquiring thing). When you’re done with your libuv extension,
could you write a blogpost or something like that summing up what you
learned while doing this extension? I know, many things are online in
the ruby-talk archives, but it would be nice if the information could be
centralised at a single place. I’m sure many Ruby developers doing more
complex C extensions would be happy to have this.
Just a suggestion. If you don’t want to, ignore it.

Sure, I’ll do it. Just let me some time until I get all this stuf
working perfectly, I’m learning every day :slight_smile:

Iñaki Baz C. писал 29.05.2012 22:37:

> > My question is: is there any way to avoid the longjmp? or a > different > approach: is there any way from my C code to avoid being interrupted > while I go to Ruby land (by acquiring the GVL)?

Let me still answer your question.

Exceptions are a mechanism of control flow which is both nonlocal and
cannot
be represented by procedure calls/returns. The only way to do that in C
is
longjmp. (In a proper language like Lisp there are also continuations,
for example.)
Well, technically there is no way to do this in C-as-a-language, and
longjmp
is a platform-specific and IIRC nonstandard extension (I’m not quite
sure,
through).

So… if you want exceptions in C, you need longjmp, period. (You can
also
avoid using system stack, i.e. represent nested Ruby calls by some
internal
structure, rather the current way where nested Ruby calls map to nested
native stack frames, but that will degrade performance significantly.)

Thread#kill and exit() both send signals. POSIX threads are a weird
kind of
processes, hence they can receive signals too, and Thread#kill works
that way.
Actually, signals are a mechanism of asynchronous prioritized execution
of code
in a context of another thread, and, surprise, there is no way to do
that in
C. (Signals are also pretty evil, too, because not only they are
asynchronous,
but also there is no sane way to synchronize their execution with main
control
flow, which is the reason 99% of signal handlers are of form
“void signal() { flag = 1; }”.)

So, if you want to deliver asynchronous external events to your program
without
an explicit event loop (which you will probably need anyway, see
above), you
need to use signals, period.

I don’t really think that there is some magical (i.e. nonstandard) way
to avoid
signal/longjmp-caused interruptions for your code. I would also really
discourage
you from messing with signals: they’re already a big pile of crap,
don’t add even
more compelxity there. Longjmp is, too, but at least it’s synchronous.

What about using setcontext - Wikipedia ? Seems
slightly
cleaner.

Jos

2012/5/29 Peter Z. [email protected]:

structure, rather the current way where nested Ruby calls map to nested
but also there is no sane way to synchronize their execution with main
avoid
signal/longjmp-caused interruptions for your code. I would also really
discourage
you from messing with signals: they’re already a big pile of crap, don’t add
even
more compelxity there. Longjmp is, too, but at least it’s synchronous.

Hi Peter, thanks a lot for your explanation. It’s clear and I think I
found the way to go:

My C exten runs a libuv (GitHub - joyent/libuv: Go to) C loop
without GVL and when an event occurs (data received, timer fires…)
it acquires the GVL and executes the user provider Ruby callback in
Ruby land. In this trip from C to Ruby it could occur that, when Ruby
takes the control, it decides to generate a SystemExit or whatever
interruption (due to a non-trapped signal, exit() command or
whatever). If I set a rescue/ensure after my Ruby blocking function
(which started the libuv loop) then libuv gets broken due the longjmp
and I cannot recover it.

So the solution has been to use rb_protect() when executing the Ruby
callback and catch any kind of exception:


static
VALUE
execute_function_with_glv_and_rb_protect(function_with_gvl_and_protect
function)
{
int exception_tag = 0;
VALUE ret;

ret = rb_protect(function, Qnil, &exception_tag);

// If an exception occurred then call to handle_exception() method.
if (exception_tag) {
VALUE exception = rb_errinfo();

// Dissable the current thread exception.
rb_set_errinfo(Qnil);
// Call AsyncEngine.handle_exception().
rb_funcall2(mAsyncEngine, method_handle_exception, 1, &exception);

return exception;

}
// Otherwise just return the VALUE returned by rb_protec() above.
else
return ret;
}

/*

  • Executes the given function taking the GVL and using rb_protect().
    */
    VALUE ae_execute_in_ruby_land(function_with_gvl_and_protect function)
    {
    AE_TRACE();

return
rb_thread_call_with_gvl(execute_function_with_glv_and_rb_protect,
function);
}

So when a libuv C callback is called, I call within it to
ae_execute_in_ruby_land(some_C_function). If an exception has occurred
during the trip to Ruby I call to my handle_exception() Ruby method by
passing the exception. It decides whether to absorb it ot store it in
@_exit_exception and properly release/stop the libuv loop, so after it
ends the @_exit_exception will be raised.

It seems to work ok. So basically I’ve avoided the longjmp while my
C libuv loop is running.

Really thanks a lot for your explanation and help.

Jos B. писал 30.05.2012 01:29:

What about using setcontext - Wikipedia ? Seems
slightly
cleaner.

Jos

It’s pretty much the same thing, just a bit more generic.