Per Mildner schrieb:
> In effect, the undo-goal may be run with a term that
> contains dangling pointers, or at least terms (or mutable values)
> different from those used when undo/1 was called.
I see this in call cleanup. Not in the sense that there
are less bindings when cleanup is called compared to the
bindings when the cleanup was installed. But in the sense
that there are more bindings when the cleanup is called.
Here is an example:
?- setup_call_cleanup(true,(X=1;X=2),(write(X),nl)), !.
1
X = 1
But the ISO draft requires that during an exception event
the bindings are undone before calling the cleanup, so
that we have (works only since 0.9.3 in Jekejeke Prolog):
?- setup_call_cleanup(true,(X=1;X=2),(write(X),nl)), throw(x).
_A
Unknown exception: x
Now what has this to do with undo/1 (respectively sys_unbind/1
how it is now called in Jekejeke Prolog since 0.9.3). Well I
came to the conclusion that it is not possible to make this
distinction for undo/1.
The undo handler is called when the bindings are undo. This
is a loop similar to the cleanup loop that cleans the choice
points (very simplified, in the realization condition for
cleanup is not a mark as depicted below).
cleanup loop:
while (cp != mark) {
/* cleanup choice point */
cp = cp.next;
}
undo loop:
while (bind != mark) {
/* undo binding */
bind = bind.next;
}
Now for a handler posited by sys_unbind/1, the handler will
be called in the /* undo binding section */. This is how
sys_unbind/1 is implemented in Jekejeke Prolog. It sits in
a different datastructure from the choice points and it
reacts on different events. So its different from
setup_call_cleanup.
So from the above we see that the handler will not have
any new bindings available, since it is part of the undo
loop, and thus the bindings are anyway gone when the handler
is called.
Fear I had was that the above is not performant enough.
Since the undo loop needs to check whether it will do a
normal variable undo or whether it needs to invoke the handler.
Also fear was that the handler will confuse the undo
loop itself.
But so far both pitfals did not materialize. There is actually
a performance drop, especially since the undo loop needs more
context parameters and better error handling. But the performance
drop is very small (~1% observed so far). And the undo loop
gets not broken, since the undo handler is called in a way
so that all new bindings are undone before returning.
But I guess other designs, for example using a separate list
for the undo handlers, could lead to the problem you describe.
When undo handlers are not called in the same order as normal
variable bindings are undone.
Unfortunately my design generates one more case where the
interpreter needs native stack. I have currently the same
problem with catch/3 and sys_cleanup/1. Its not a design
where we stay inside an interpreter loop. The interpreter
loop is recursively called inside the undo loop.
I have a simple idea how to avoid native stack for the catch/3.
But I am not yet close to any simple idea for sys_cleanup/1 or
sys_undo/1, except for building an elaborate state machine from
the sequential solution. Maybe this could be also a reason for
alternative designs and other pitfals.
Bye