From: "Iñaki Baz Castillo" Date: 2012-05-30T20:26:10+09:00 Subject: Re: [C exten] Is it possible to avoid longjmp in exceptions, Thread#kill, exit(), signals? 2012/5/29 Peter Zotov : > 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. 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. -- Iñaki Baz Castillo