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 |
|
Another thing to consider is whether the implementation of this feature |
25 |
|
is always going to be tied to the dynamic-root concept, or whether |
26 |
|
it could be implemented in some other way. |
27 |
|
|
28 |
|
Finally, there is the consideration of whether all this is just more |
29 |
|
trouble than it's worth: perhaps the applications that want this |
30 |
|
feature should either a) protect their code properly against Scheme |
31 |
|
flow-control b) forbid use of call/cc in callbacks c) forbid accessing |
32 |
|
the top-level environment in callbacks. |
33 |
|
|
34 |
Example |
Example |
35 |
======= |
======= |
36 |
|
|
37 |
We have a procdure that executes a procedure supplied by a user. |
We have some code that executes a procedure supplied by a user. |
38 |
It's essential that the code before and after the user-proc is |
It's essential that the code before and after the user-proc is |
39 |
executed in the right order: maybe it's C code that opens and |
executed in the right order: maybe it's C code that opens and |
40 |
closes files or allocates/frees memory: |
closes files or allocates/frees memory: |
62 |
|
|
63 |
"... done" |
"... done" |
64 |
|
|
65 |
|
i.e., the user has jumped back into the callback via a continuation. |
66 |
|
|
67 |
|
Alternatively, the user can jump out of the callback: |
68 |
|
|
69 |
|
(define cont #f) |
70 |
|
(call-with-current-continuation (lambda (c) (set! cont c))) |
71 |
|
(break-me (lambda () |
72 |
|
(cont))) |
73 |
|
|
74 |
Discussion |
Discussion |
75 |
========== |
========== |
76 |
|
|
77 |
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: |
78 |
raising an exception or invoking a continuation. Exceptions can be |
raising an exception or invoking a continuation. Exceptions can be |
79 |
caught using scm_internal_catch, but a primitive "continuation |
caught using scm_internal_catch, but a primitive "continuation |
80 |
barrier" is lacking. However such a barrier can be easily created |
barrier" is lacking. A reentry barrier can be easily created |
81 |
using dynamic-wind, assuming that an error can be signalled within the |
using dynamic-wind, assuming that an error can be signalled within the |
82 |
before- thunk: |
before- thunk: |
83 |
|
|
84 |
(define with-continuation-barrier |
(define with-reentry-barrier |
85 |
(lambda (thunk) |
(lambda (thunk) |
86 |
(let ((entered #f)) |
(let ((entered #f)) |
87 |
(dynamic-wind |
(dynamic-wind |
88 |
(lambda () |
(lambda () |
89 |
(if entered |
(if entered |
90 |
(error "Attempt to break continuation barrier") |
(error "Attempt to break reentry barrier") |
91 |
(set! entered #t))) |
(set! entered #t))) |
92 |
thunk |
thunk |
93 |
(lambda () #f))))) |
(lambda () #f))))) |
94 |
|
|
95 |
The example above can now be fixed: |
The jump-back-in example above may now be fixed: |
96 |
|
|
97 |
(define break-me (lambda (user-proc) |
(define break-me (lambda (user-proc) |
98 |
(display "executing user ...\n") |
(display "executing user ...\n") |
99 |
(with-continuation-barrier user-proc) |
(with-reentry-barrier user-proc) |
100 |
(display "... done\n"))) |
(display "... done\n"))) |
101 |
|
|
102 |
The interesting problem is the interaction between exceptions and |
However the jump-out example is resistent to this technique. In any |
103 |
continations. While exceptions that occur in user code should be |
case the interaction of dynamic-wind and continuations and exceptions |
104 |
caught so that the cleanup code can be executed, the cleanup code |
is likely to cause strange and unstable behaviour: |
|
should not be called for the exception raised by |
|
|
with-continuation-barrier itself. Thus the exception handler needs to |
|
|
go inside the continuation barrier. This also avoids any assumption |
|
|
that exceptions can pass through the continuation barrier, i.e., |
|
|
current Guile behaviour where exceptions are indendent of |
|
|
continuations. |
|
|
|
|
|
A problem with this is that an outer exception handler would catch |
|
|
both exceptions from the user code and exceptions generated by the |
|
|
continuation barrier: i.e., possibly more than once for a given |
|
|
invocation. The current call-with-dynamic-root implementation avoids |
|
|
this by refusing to invoke mismatching continuations in the first |
|
|
place, by examining a sequence number in the dynamic root. However |
|
|
it's not clear that presence of the dynamic root implementation is |
|
|
something that can be relied on in the long term. |
|
105 |
|
|
106 |
(define (break-me user-proc) |
guile> (define (break-me user-proc) |
107 |
(catch #t |
(catch #t |
108 |
(lambda () |
(lambda () |
109 |
(display "executing user ...\n") |
(display "executing user ...\n") |
110 |
(with-continuation-barrier user-proc) |
(with-reentry-barrier user-proc) |
111 |
(display "... done\n")) |
(display "... done\n")) |
112 |
(lambda args |
(lambda args |
113 |
(display "outer catch!\n") |
(display "outer catch!\n") |
123 |
guile> (cont #f) |
guile> (cont #f) |
124 |
Segmentation fault |
Segmentation fault |
125 |
|
|
126 |
Hmmm, it's even worse than I thought. |
The existing dynamic-root method avoids such difficulties by |
127 |
|
preventing the execution of continuations that would cross the |
128 |
|
continuation barrier, instead of trying to clean things up after the |
129 |
|
continuation has been executed. |
130 |
|
|
131 |
For ease of use, we would like to have a single facility to set up |
For ease of use, we would like to have a single facility to set up |
132 |
both continuation and exception handlers. Such an interface should |
both continuation and exception handlers. Such an interface should |