[Vm-dev] Re: Handling exceptions during callback

Andreas Raab andreas.raab at gmx.de
Tue May 4 04:14:51 UTC 2010

Hi Igor -

I don't think there's a general approach for the problem. After quickly 
checking our internal uses of callbacks I found that usage basically 
looks like this:

	"Run the callback process"
	[true] whileTrue:[
		CallbackSemaphore wait.
		[self handleCallback] ensure:[self callbackReturn].

	"Handle a callback"
	| nArgs rcvr selector args result |
	nArgs := self callbackGetArgCount.
	rcvr := self callbackGetArg: 1.
	selector := self callbackGetArg: 2.
	args := (3 to: nArgs) collect:[:i| self callbackGetArg: i].
	result := [rcvr callback: selector asSymbol args: args] on: Error do:[:ex|
		ex return: nil.
	self callbackResult: result.

(this is slightly simplified from original code) The point there is that 
if there's an error during the callback we simply return some predefined 
value (nil in the above) and assume that the caller can deal with that 
default return value.

The alternative (that I had considered but dropped as adding too much 
complexity for to little value) was having an explicit error indication 
along the lines of:

	result := [rcvr callback: selector asSymbol args: args] on: Error do:[:ex|
		"Signal underlying code that we failed"
		self callbackError: ex description.
		ex return: nil. "still sets a default return value"
	self callbackResult: result.

But this then assumes that the callback machinery itself has some notion 
of failure which is generally not the case.

The interesting point here is that since callbacks *can* fail there must 
be some way by which one can indicate to the caller that a failure has 
occurred. However, that is exactly why I decided against having the 
callbackError: call - when you handle the callbacks you absolutely need 
to wrap them properly in an error handler and do whatever is appropriate 
to return from the callback in the case of an error. And that, of 
course, is specific to the callback in question and cannot be 
implemented by the callback machinery in general.

And if your callback has no way to indicate failure, then you've got two 
choices: 1) Return some madeup value or 2) Fail and die. There are 
really no other options.

So the short answer to your question is that to implement a callback 
properly you *must* specify how to respond in the case of an error. It's 
not optional and in fact, you might make that part of the interface of a 
callback, i.e., *always* require an error handler for the client to 
indicate how to respond to an error. This could be as simple as 
something like:

	Callback action:[self doSomething] ifError:[0]. "returns zero on errors"

or it could be more complex, but you might consider making this 
explicitly part of the callback interface.

The other issue in this regard is how to debug failures. We punt on 
this. We print a callstack to indicate error and then we return the 
default value. So there's no other information than the callstack. You 
probably want to look at Newspeak to find out how to debug callbacks; 
there must be a few 'tricks' for how to ensure proper debug and return 
semantics. We don't have any such uses in Teleplace at this point.

   - Andreas

On 5/3/2010 4:31 PM, Igor Stasenko wrote:
> Hello,
> 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.
> 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 block.
> 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.

More information about the Vm-dev mailing list