2012/5/29 Peter Zotov <whitequark / whitequark.org>:
> 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 thatin
> 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.


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 (https://github.com/joyent/libuv/) 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.




-- 
Iaki Baz Castillo
<ibc / aliax.net>