Jonathan Paisley
jp-ww****@dcs*****
Mon Aug 7 06:22:26 JST 2006
On 4 Aug 2006, at 20:03, Tim Burks wrote: > But unfortunately it's not that simple. When I tested this, my > application exited with "RBException_RuntimeError - continuation > called across trap". > > Looking into it, I saw that each Objective-C call to Ruby is wrapped > in a call to rb_protect so that Ruby exceptions can be caught. But > this also sets a "cont_protect" variable that tracks the contexts in > which a continuation is created and called, and these contexts must > be the same for the call to be successful. Unfortunately, each > rb_protect call sets cont_protect to a unique value. > > So it looks like we cannot use continuations created in one > invocation from any other invocation. > > Has anyone tried to remove this limitation? I had more of a read through eval.c to see what it does with continuations. I think that the reason for the cont_protect stuff you've identified is to prevent continuations being used across extension library code, because there could then be scope for resources not being freed properly. In Objective-C land, things are particularly problematic due to the autorelease pool and exception handlers, particularly because the C stack changes between Ruby threads. This is exactly the problem we've encountered with using Ruby-level threads in an ObjC application. My patch to the ruby interpreter explicitly switches the autorelease pool and exception handler stack in and out on Ruby thread switches. The same patch code would kick in when using continuations, since continuations are implemented in Ruby by creating a Ruby thread. This raises an issue that I hadn't been aware of before. Our current usage of rb_protect also handles 'throw :XXX', but incorrectly packages them up as a ruby exception. It's vital that we catch any longjmp-ing done by the ruby interpreter, in order to honour any objective c exception handlers and finally clauses. It seems, therefore, that symbols thrown in ruby-land should probably be wrapped in a special NSException and rethrown where appropriate. Unfortunately, the TAG_* constants defined in eval.c are not public, and it is these that are the value of the 'state' byref argument to rb_protect. The TAG_* constants tells us what kind of thing has happened. For example, TAG_RAISE is used for exceptions and TAG_THROW is used for thrown symbols. Furthermore, the symbol that has actually been thrown is stored in the private 'struct tag.dst' field. So, there's no easy way to wrap up the 'throw' properly in an NSException, because we can't find what symbol was thrown. It might be possible to just wrap up the 'state' value in a special exception, and call rb_jump_tag() at the appropriate time (upon return from an objc method invocation from the bridge). I think exception handling/wrapping is something that needs to be looked at in more detail anyway... The starting point would probably be some solid examples along with explanations of current and desired behaviour. Getting back to the continuation issue... We can't use rb_protect if you want to use continuations. We could use rb_rescue2 instead, but this doesn't provide safety for throw/catch across objective c code. If you're willing to ensure that throw/catch is never used across objective c code, then that is a possible compromise. However, the thread scheduling patches to RubyCocoa and Ruby will still be required. Sorry for the long-winded reply. Hopefully you can make some sense out of it! I think implementing the thing using threads would probably be the most straightforward solution. Cheers, Jonathan