21 |
and reporting; whether a new interface is convenient to use from C; |
and reporting; whether a new interface is convenient to use from C; |
22 |
whether a new interface should also be available to Scheme code. |
whether a new interface should also be available to Scheme code. |
23 |
|
|
24 |
|
Example |
25 |
|
======= |
26 |
|
|
27 |
|
We have a procdure that executes a procedure supplied by a user. |
28 |
|
It's essential that the code before and after the user-proc is |
29 |
|
executed in the right order: maybe it's C code that opens and |
30 |
|
closes files or allocates/frees memory: |
31 |
|
|
32 |
|
(define break-me (lambda (user-proc) |
33 |
|
(display "executing user ...\n") |
34 |
|
(user-proc) |
35 |
|
(display "... done\n"))) |
36 |
|
|
37 |
|
Now the user types: |
38 |
|
|
39 |
|
(define cont #f) |
40 |
|
(break-me (lambda () |
41 |
|
(call-with-current-continuation (lambda (c) |
42 |
|
(set! cont c))))) |
43 |
|
|
44 |
|
giving: |
45 |
|
|
46 |
|
"executing user ... |
47 |
|
... done" |
48 |
|
|
49 |
|
but then: |
50 |
|
|
51 |
|
(cont #f) gives: |
52 |
|
|
53 |
|
"... done" |
54 |
|
|
55 |
Discussion |
Discussion |
56 |
========== |
========== |
57 |
|
|
58 |
There are two ways that longjmp may be invoked from a Scheme callback: |
There are two ways that longjmp may be invoked from a Scheme callback: |
59 |
raising an exception or invoking a continuation. Exceptions can be |
raising an exception or invoking a continuation. Exceptions can be |
60 |
caught using scm_internal_catch, so it could be argued that the new |
caught using scm_internal_catch, but a primitive "continuation |
61 |
interface only needs to block continuations. |
barrier" is lacking. However such a barrier can be easily created |
62 |
|
using dynamic-wind, assuming that an error can be signalled within the |
63 |
However there are two problems with this: firstly it's unlikely that |
before- thunk: |
64 |
anybody would want to block continuations without also catching |
|
65 |
exceptions, so it's more convenient to use a single facility set up |
(define with-continuation-barrier |
66 |
both types of blocking. Secondly, the fact that exceptions and |
(lambda (thunk) |
67 |
continuations can be treated separately in Guile is just an |
(let ((entered #f)) |
68 |
implementation detail: in general in Scheme it's possible to use |
(dynamic-wind |
69 |
continuations to implement an exception mechanism, and it's |
(lambda () |
70 |
undesirable to tie a new language feature to an implementation detail |
(if entered |
71 |
when it can be avoided, even at the C level. |
(error "Attempt to break continuation barrier") |
72 |
|
(set! entered #t))) |
73 |
Hence, the interface should take at least a) the callback to be |
thunk |
74 |
protected b) and exception handler and associated handler data to be |
(lambda () #f))))) |
75 |
passed to scm_internal_catch. |
|
76 |
|
The example above can now be fixed: |
77 |
On which side of the continuation barrier should be exception handler |
|
78 |
be installed? Logically it belongs on the same side as the callback: |
(define break-me (lambda (user-proc) |
79 |
i.e., if the callback raises an exception then the handler can catch |
(display "executing user ...\n") |
80 |
it without crossing it the continuation barrier. But what happens if |
(with-continuation-barrier user-proc) |
81 |
the handler raises another exception? This doesn't seem like an |
(display "... done\n"))) |
82 |
important concern, since the hander is under control of the code that |
|
83 |
is trying to protect itself. It should be sufficient to warn in the |
The interesting problem is the interaction between exceptions and |
84 |
documentation that such exceptions produce undefined behaviour and |
continations. While exceptions that occur in user code should be |
85 |
allow them to cross the continuation barrier. |
caught so that the cleanup code can be executed, the cleanup code |
86 |
|
should not be called for the exception raised by |
87 |
|
with-continuation-barrier itself. Thus the exception handler needs to |
88 |
|
go inside the continuation barrier. This also avoids any assumption |
89 |
|
that exceptions can pass through the continuation barrier, i.e., |
90 |
|
current Guile behaviour where exceptions are indendent of |
91 |
|
continuations. |
92 |
|
|
93 |
|
A problem with this is that an outer exception handler would catch |
94 |
|
both exceptions from the user code and exceptions generated by the |
95 |
|
continuation barrier: i.e., possibly more than once for a given |
96 |
|
invocation. The current call-with-dynamic-root implementation avoids |
97 |
|
this by refusing to invoke mismatching continuations in the first |
98 |
|
place, by examining a sequence number in the dynamic root. However |
99 |
|
it's not clear that presence of the dynamic root implementation is |
100 |
|
something that can be relied on in the long term. |
101 |
|
|
102 |
|
(define (break-me user-proc) |
103 |
|
(catch #t |
104 |
|
(lambda () |
105 |
|
(display "executing user ...\n") |
106 |
|
(with-continuation-barrier user-proc) |
107 |
|
(display "... done\n")) |
108 |
|
(lambda args |
109 |
|
(display "outer catch!\n") |
110 |
|
(write args)))) |
111 |
|
|
112 |
|
guile> (define cont #f) |
113 |
|
(break-me (lambda () |
114 |
|
(call-with-current-continuation (lambda (c) |
115 |
|
(set! cont c))))) |
116 |
|
|
117 |
|
guile> executing user ... |
118 |
|
... done |
119 |
|
guile> (cont #f) |
120 |
|
Segmentation fault |
121 |
|
|
122 |
|
Hmmm, it's even worse than I thought. |
123 |
|
|
124 |
|
For ease of use, we would like to have a single facility to set up |
125 |
|
both continuation and exception handlers. Such an interface should |
126 |
|
take at least a) the callback to be protected b) and exception handler |
127 |
|
and associated handler data to be passed to scm_internal_catch. |
128 |
|
|
129 |
How should the callback procedure be passed to the interface and |
How should the callback procedure be passed to the interface and |
130 |
invoked? Should it be like scm_internal_catch where it's passed as a |
invoked? Should it be like scm_internal_catch where it's passed as a |
155 |
of calling the handler. So the return type is SCM, as for |
of calling the handler. So the return type is SCM, as for |
156 |
scm_internal_catch. |
scm_internal_catch. |
157 |
|
|
158 |
Yet to be discussed: libguile usage and threads, error handling and |
To do |
159 |
reporting, convenience of use, Scheme-level interface. |
===== |
160 |
|
|
161 |
|
libguile usage and threads, error handling and reporting, convenience |
162 |
|
of use, Scheme-level interface. |