[Vm-dev] Re: Handling exceptions during callback
eliot.miranda at gmail.com
Tue May 4 20:24:29 UTC 2010
On Mon, May 3, 2010 at 4:31 PM, Igor Stasenko <siguctua at gmail.com> wrote:
> Andreas and Eliot,
> i direct my question mainly to you , because you are most experienced
> in this area, but if there's others who want to share their thoughts,
> please do so.
> So, the question is, what to do, if an error triggered during a callback.
> My concern is, that sometimes its unsafe to do return from callback
> with invalid value.
At one (low) level it depends on what kind of code you're calling.
Essentially the problem is how to cut-back the C call stack. Since the FFI
machinery knows the extent of the C stack between the FFI callout and the
callback it is straight-forward to provide an escape primitive that cuts
back the C stack to the point of the FFI callout within which the callback
occurred. But one can't simply cut-back the stack if one is calling code
that depends on unwind-protect (e.g. C++). If you want to support C++ the
FFI callout could use a try-catch around the actual callout (something you'd
want to do anyway if you wanted the FFI to be able to catch exceptions and
answer them as primitive failures). To cut-back the stack the primitive
set a flag and throw a special exception that would be caught by the
try-catch in the FFI callout. The catch block would test for the flag and
do a special return, e.g. return a failure value or fail the FFI callout
primitive. I like the primitive failure response because the response to
FFI failures could convert the failure into a Smalltalk exception that could
be engineered to continue propagating the exception that caused you to want
to unwind the callback.
Another approach is to say that this is a fatal error in a deployed
application (because, as you can see from the above, you need some complex
machinery to deal with the situation generally) and that during development
all one is able to do is inform the programmer of what has happened so they
can try and avoid it.
Another approach (which IIRC is the one VisualWorks takes) is just to return
some default error value to the callback. The VW machinery puts a generic
exception handler round the invocation of Smalltalk code from the callback
trampoline so that it catches any uncaught exception raised within the
Smalltalk side of the callback. The exception handler arranges to answer a
default value to the callback and consume the exception.
I think you'll be able to find some papers on propagating exceptions across
language boundaries (which is what this is really about) and see what
high-quality FFIs do here. Finding the cost/benefit justification for
implementing it is another thing altogether :) It seems to me that a
reasonable approach is as follows*:*
1. no attempt is made to propagate Smalltalk exceptions to foreign code or
vice verse. e.g. if an exception occurs in Smalltalk code invoked in the
context of a callback then it can propagate freely beyond the FFI callout
within which the callback occurred.
2. given that exceptions are not propagated what we require is correct
propagation of unwinds (invocation of finally blocks in C++) to foreign code
if and when the Smalltalk stack is unwound around an FFI callout/callback
To implement this one needs two things
- to put an unwind-protect around the invocation of the Smalltalk code from
- to provide a primitive that will unwind the C stack back from a callback
to its corresponding FFI callout /and not/ return from the FFI callout,
instead somehow returning from the primitive invocation.
The last thing isn't that hard to do because we have contexts. The
unwind-a-callback primitive can save the calling context and the try-catch
in the FFI callout can arrange to return to it, not to the sender of the FFI
So when the Smalltalk stack is unwound as the last part of handling the
exception within the callback we cause any finally blocks in C++ to be run
as the C stack is unwound from our special callback-unwind primitive. A
stack containing multiple callout/callback invocations can be correctly
unwound over multiple callout/callback invocations. If within a
callout/callback invocation there are callouts and callbacks to other
language systems invisible to us then providing these also support correct
unwinding we won't break anything because we're unwinding correctly.
Does this make any sense?
> For example, if callback's caller expects a pointer to something,
> returning a default value (NULL)
> will have the same effect as simply terminating VM :)
> Here's my snippet of code, which i coded so far:
> block: aBlock
> self numberOfArguments = aBlock numArgs ifFalse: [
> self error: 'Invalid number of arguments' ].
> context := [ "here we entering a callback"
> [ | args | args := self readArguments.
> self leave: (aBlock valueWithArguments: args) ]
> ifCurtailed: [ self error: 'attempt to non-local
> return across a callback' ]
> ] repeat.
> ] asContext
> when callback is activated, an interpreter should enter the context ,
> stored in callback's context ivar.
> The best what i invented so far, is to wrap the whole thing in repeat
> So, if there's error happens, there could be at least chance that user
> (like me ;) might supply the right argument to the #leave: method,
> using debugger.
> A leave: method serves to leave callback , but also might fail, if it
> can't coerce a return value to corresponding C equivalent.
> Of course, there's many other variants, like
> self leave: ( [ aBlock valueWithArguments: args ] ifCurtailed: [ self
> askUserToEnterTheValue ] )
> i'd like to know, what you think would be the best variant.
> Best regards,
> Igor Stasenko AKA sig.
-------------- next part --------------
An HTML attachment was scrubbed...
More information about the Vm-dev