Let's start Scheme

2016-07-29

Exception handling in C world

The following piece of code goes into infinite loop on Sagittarius.
(import (rnrs))
(with-exception-handler
 (lambda (k) #t)
 (lambda ()
   (guard (e (#f #f))
     (error 'test "msg"))))
I couldn't figure it out why this never returned at first glance. And it turned out to be very interesting problem.

The problem is related to continuation boundary and exception handlers. If you print the k, then only the very first time you'd see error object with "msg" after that it'd be "attempt to return from C continuation boundary.". But why?

This happens when you capture a continuation and invoke it in the different C level apply. Each time C level apply is called, then VM put a boundary mark on its stack to synchronise the C level stack and VM stack. This is needed because there's no way to restore C level stack when the function is returned. When a continuation is captured on the C apply which is already returned, then invocation of this continuation would cause unexpected result. To avoid this, the VM checks if the continuation is captured on the same C level apply.

Still, why this combination causes this? The answer is raise is implemented in C world and exception handlers are invoked by C level apply. The whole step of the infinite loop is like this:
  1. guard without else clause captures a continuation.
  2. When error is called, then guard re-raise and invoke the captured continuation. (required by R6RS/R7RS)
  3. Exception handler is invoked by C level apply.
  4. VM check the continuation boundary and raises an error on the same dynamic environment as #3
  5. Goes to #3. Hurray!
There are 2 things I can do:
  1. Create a specific condition and when with-exception-handler received it, then it wouldn't restore the exception handler. [AdHoc]
  2. Let raise use the Scheme level apply. (Big task)
#1 probably wouldn't work properly. #2 is really a huge task. I need to think about it.

No comments:

Post a Comment