From: Spiros Bousbouras
Subject: Three questions about UNWIND-PROTECT
Date: 
Message-ID: <768b8bb1-0f3c-47f9-b4be-cfa50f63a9bb@u36g2000prf.googlegroups.com>
The HS page has the following example:

 (tagbody
   (let ((x 3))
     (unwind-protect
       (if (numberp x) (go out))
       (print x)))
  out
   ...)

Then it says:

    When go is executed, the call to print is executed
    first, and then the transfer of control to the tag
    out is completed.

Now compare the above with the following example:

 (defun dummy-function (x)
    (setq state 'running)
    (unless (numberp x) (throw 'abort 'not-a-number))
    (setq state (1+ x))) =>  DUMMY-FUNCTION
 (catch 'abort (dummy-function 1)) =>  2
 state =>  2
 (catch 'abort (dummy-function 'trash)) =>  NOT-A-NUMBER
 state =>  RUNNING
 (catch 'abort (unwind-protect (dummy-function 'trash)
                  (setq state 'aborted))) =>  NOT-A-NUMBER
 state =>  ABORTED


Based on the behaviour of the 2nd example, it looks as
if dummy-function is called first and then the cleanup
form (setq state 'aborted) is executed. But in the 1st
example the cleanup form (print x) is executed first
and then the transfer of control happens. So why is the
behaviour different in the 2 cases ? Is it because the
compiler can see that after calling dummy-function the
flow of execution will return inside the unwind-protect
form so the call to dummy-function does not count as an
exit ?


2nd question:

A bit further down we see the following example:

    The following code is not correct:

 (unwind-protect
   (progn (incf *access-count*)
          (perform-access))
   (decf *access-count*))

    If an exit occurs before completion of incf, the
    decf form is executed anyway, resulting in an
    incorrect value for *access-count*. The correct
    way to code this is as follows:

 (let ((old-count *access-count*))
   (unwind-protect
     (progn (incf *access-count*)
            (perform-access))
     (setq *access-count* old-count)))

How can an exit occur before completion of incf ?
And in the "correct" code what happens if there is
no exit ? (setq *access-count* old-count) will be
executed and *access-count* is still going to have
the wrong value. I think I may be misunderstanding
what the whole set-up is supposed to be or I'm
missing some unstated assumptions about
perform-access


3rd question:

Again from the HS page:

    ;;; The following has undefined consequences.
     (block a
       (block b
         (unwind-protect (return-from a 1)
           (return-from b 2))))

Why is it undefined ? I can see it doesn't make
sense but what rules does it violate ?

From: Ron Garret
Subject: Re: Three questions about UNWIND-PROTECT
Date: 
Message-ID: <rNOSPAMon-E67941.13153515042008@news.gha.chartermi.net>
In article 
<····································@u36g2000prf.googlegroups.com>,
 Spiros Bousbouras <······@gmail.com> wrote:

> The HS page has the following example:
> 
>  (tagbody
>    (let ((x 3))
>      (unwind-protect
>        (if (numberp x) (go out))
>        (print x)))
>   out
>    ...)
> 
> Then it says:
> 
>     When go is executed, the call to print is executed
>     first, and then the transfer of control to the tag
>     out is completed.
> 
> Now compare the above with the following example:
> 
>  (defun dummy-function (x)
>     (setq state 'running)
>     (unless (numberp x) (throw 'abort 'not-a-number))
>     (setq state (1+ x))) =>  DUMMY-FUNCTION
>  (catch 'abort (dummy-function 1)) =>  2
>  state =>  2
>  (catch 'abort (dummy-function 'trash)) =>  NOT-A-NUMBER
>  state =>  RUNNING
>  (catch 'abort (unwind-protect (dummy-function 'trash)
>                   (setq state 'aborted))) =>  NOT-A-NUMBER
>  state =>  ABORTED
> 
> 
> Based on the behaviour of the 2nd example, it looks as
> if dummy-function is called first and then the cleanup
> form (setq state 'aborted) is executed. But in the 1st
> example the cleanup form (print x) is executed first
> and then the transfer of control happens. So why is the
> behaviour different in the 2 cases ?

It isn't.  The transfer of control happens "first" in both cases.  The 
cleanup form is executed "on the way out", i.e. after the transfer of 
control is initiated, but before it is completed.

The example in the spec is not a good example because it does exactly 
the same thing whether or not X is a number (to say nothing of the fact 
that the conditional can be constant-folded away).


> 2nd question:
> 
> A bit further down we see the following example:
> 
>     The following code is not correct:
> 
>  (unwind-protect
>    (progn (incf *access-count*)
>           (perform-access))
>    (decf *access-count*))
> 
>     If an exit occurs before completion of incf, the
>     decf form is executed anyway, resulting in an
>     incorrect value for *access-count*. The correct
>     way to code this is as follows:
> 
>  (let ((old-count *access-count*))
>    (unwind-protect
>      (progn (incf *access-count*)
>             (perform-access))
>      (setq *access-count* old-count)))
> 
> How can an exit occur before completion of incf ?

If you hit ctrl-C for example.

> And in the "correct" code what happens if there is
> no exit ? (setq *access-count* old-count) will be
> executed and *access-count* is still going to have
> the wrong value. I think I may be misunderstanding
> what the whole set-up is supposed to be or I'm
> missing some unstated assumptions about
> perform-access

The name *access-count* is misleading.  It's not a count of the number 
of times the resource has been accessed, it's a count of the number of 
processes that are currently in the process of accessing the resource.


> 3rd question:
> 
> Again from the HS page:
> 
>     ;;; The following has undefined consequences.
>      (block a
>        (block b
>          (unwind-protect (return-from a 1)
>            (return-from b 2))))
> 
> Why is it undefined ? I can see it doesn't make
> sense but what rules does it violate ?

Looks like a bug in the spec to me.  CLisp and CCL both return 2, which 
seems to me to be as it should be.

rg
From: Thomas A. Russ
Subject: Re: Three questions about UNWIND-PROTECT
Date: 
Message-ID: <ymitzi24u1t.fsf@blackcat.isi.edu>
Ron Garret <·········@flownet.com> writes:

> > 3rd question:
> > 
> > Again from the HS page:
> > 
> >     ;;; The following has undefined consequences.
> >      (block a
> >        (block b
> >          (unwind-protect (return-from a 1)
> >            (return-from b 2))))
> > 
> > Why is it undefined ? I can see it doesn't make
> > sense but what rules does it violate ?
> 
> Looks like a bug in the spec to me.  CLisp and CCL both return 2, which 
> seems to me to be as it should be.

Well, I think you could argue that the transfer of control from inside
the UNWIND-PROTECT (to "a" with value 1) should be done AFTER the
cleanup forms run, so that it would end up happening after the first
return-from and thus end up trumping the cleanup form's return.

On the other hand, it may be easier to implement what CLisp and CCL (and
ACL, too) do.

But what would the expected behavior of

  (block a
    (block b
      (unwind-protect (return-from a 1)
         (return-from b 2)
         (return-from b 3))))

be?  Should it be 2 or 3?  What about the "guarantee" that all of the
cleanup forms are run?

-- 
Thomas A. Russ,  USC/Information Sciences Institute
From: Ron Garret
Subject: Re: Three questions about UNWIND-PROTECT
Date: 
Message-ID: <rNOSPAMon-5F5BA1.18400415042008@news.gha.chartermi.net>
In article <···············@blackcat.isi.edu>,
 ···@sevak.isi.edu (Thomas A. Russ) wrote:

> Ron Garret <·········@flownet.com> writes:
> 
> > > 3rd question:
> > > 
> > > Again from the HS page:
> > > 
> > >     ;;; The following has undefined consequences.
> > >      (block a
> > >        (block b
> > >          (unwind-protect (return-from a 1)
> > >            (return-from b 2))))
> > > 
> > > Why is it undefined ? I can see it doesn't make
> > > sense but what rules does it violate ?
> > 
> > Looks like a bug in the spec to me.  CLisp and CCL both return 2, which 
> > seems to me to be as it should be.

For the record, I was mistaken.  This is not a bug in the spec.  Section 
5.2 describes why the behavior is undefined.  Nonetheless...

> Well, I think you could argue that the transfer of control from inside
> the UNWIND-PROTECT (to "a" with value 1) should be done AFTER the
> cleanup forms run,

You could argue that, but you'd be wrong ;-)

> so that it would end up happening after the first
> return-from and thus end up trumping the cleanup form's return.

No, that is not what happens.

> On the other hand, it may be easier to implement what CLisp and CCL (and
> ACL, too) do.
> 
> But what would the expected behavior of
> 
>   (block a
>     (block b
>       (unwind-protect (return-from a 1)
>          (return-from b 2)
>          (return-from b 3))))
> 
> be?  Should it be 2 or 3?

Well, it's undefined, but under no (reasonable) circumstances could it 
return 3.

>  What about the "guarantee" that all of the
> cleanup forms are run?

There is no such guarantee.

"If a non-local exit occurs during execution of cleanup-forms, no 
special action is taken. The cleanup-forms of unwind-protect are not 
protected by that unwind-protect."

rg
From: Kent M Pitman
Subject: Re: Three questions about UNWIND-PROTECT
Date: 
Message-ID: <uy77eppgn.fsf@nhplace.com>
Ron Garret <·········@flownet.com> writes:

> > 3rd question:
> > 
> > Again from the HS page:
> > 
> >     ;;; The following has undefined consequences.
> >      (block a
> >        (block b
> >          (unwind-protect (return-from a 1)
> >            (return-from b 2))))
> > 
> > Why is it undefined ? I can see it doesn't make
> > sense but what rules does it violate ?
> 
> Looks like a bug in the spec to me.  CLisp and CCL both return 2, which 
> seems to me to be as it should be.

I don't think it's a bug in the spec.

As I recall, it's left undefined because the two locations might or
might not be the same continuation.  If they were different, you'd be
aborting an ongoing return and it was argued (primarily by David Moon
of Symbolics) that these were cases where if you defined this to be
workable, you couldn't guarantee that programs would ever reliably
exit because an unwind-protect could head off any exit.

Note that there's an ambiguity in this case because in some
implementations A and B will be the same continuation, but in some it
will be different.  It depends on the compiler.  But in the cases
where they are distinct, returning from b stops the return from a.

http://www.lispworks.com/documentation/HyperSpec/Issues/iss152_w.htm
From: Ron Garret
Subject: Re: Three questions about UNWIND-PROTECT
Date: 
Message-ID: <rNOSPAMon-14501A.18421415042008@news.gha.chartermi.net>
In article <·············@nhplace.com>,
 Kent M Pitman <······@nhplace.com> wrote:

> Ron Garret <·········@flownet.com> writes:
> 
> > > 3rd question:
> > > 
> > > Again from the HS page:
> > > 
> > >     ;;; The following has undefined consequences.
> > >      (block a
> > >        (block b
> > >          (unwind-protect (return-from a 1)
> > >            (return-from b 2))))
> > > 
> > > Why is it undefined ? I can see it doesn't make
> > > sense but what rules does it violate ?
> > 
> > Looks like a bug in the spec to me.  CLisp and CCL both return 2, which 
> > seems to me to be as it should be.
> 
> I don't think it's a bug in the spec.

You're right.  The reason it's undefined is in section 5.2:

"When a transfer of control is initiated by go, return-from, or throw 
the following events occur...

1. Intervening exit points are ``abandoned'' (i.e., their extent ends 
and it is no longer valid to attempt to transfer control through them)."

So by the time (return-from b 2) is executed, block b has already been 
abandoned (at least potentially).

rg
From: Ron Garret
Subject: Re: Three questions about UNWIND-PROTECT
Date: 
Message-ID: <rNOSPAMon-27E628.18435615042008@news.gha.chartermi.net>
In article <·······························@news.gha.chartermi.net>,
 Ron Garret <·········@flownet.com> wrote:

> In article <·············@nhplace.com>,
>  Kent M Pitman <······@nhplace.com> wrote:
> 
> > Ron Garret <·········@flownet.com> writes:
> > 
> > > > 3rd question:
> > > > 
> > > > Again from the HS page:
> > > > 
> > > >     ;;; The following has undefined consequences.
> > > >      (block a
> > > >        (block b
> > > >          (unwind-protect (return-from a 1)
> > > >            (return-from b 2))))
> > > > 
> > > > Why is it undefined ? I can see it doesn't make
> > > > sense but what rules does it violate ?
> > > 
> > > Looks like a bug in the spec to me.  CLisp and CCL both return 2, which 
> > > seems to me to be as it should be.
> > 
> > I don't think it's a bug in the spec.
> 
> You're right.  The reason it's undefined is in section 5.2:

For the record, this was pointed out to me by Richard Kreuter.

rg
From: Richard M Kreuter
Subject: Re: Three questions about UNWIND-PROTECT
Date: 
Message-ID: <87hce2n55a.fsf@progn.net>
Ron Garret <·········@flownet.com> writes:

>> 3rd question:
>> 
>> Again from the HS page:
>> 
>>     ;;; The following has undefined consequences.
>>      (block a
>>        (block b
>>          (unwind-protect (return-from a 1)
>>            (return-from b 2))))
>> 
>> Why is it undefined ? I can see it doesn't make
>> sense but what rules does it violate ?
>
> Looks like a bug in the spec to me.  CLisp and CCL both return 2, which 
> seems to me to be as it should be.

See CLHS section 5.2 [1].  Specifically, B is abandoned at the
beginning of processing of the first RETURN-FROM, and the consequences
of transferring control to an abandoned exit point are undefined.
(That said, I don't know offhand whether most implementations happen
to agree on what occurs in this case.)

See also the EXIT-EXTENT writeup [2].

[1] http://www.lisp.org/HyperSpec/Body/sec_5-2.html
[2] http://www.lisp.org/HyperSpec/Issues/iss152-writeup.html

--
RmK
From: Thomas A. Russ
Subject: Re: Three questions about UNWIND-PROTECT
Date: 
Message-ID: <ymiy77e4uca.fsf@blackcat.isi.edu>
Spiros Bousbouras <······@gmail.com> writes:

> The HS page has the following example:
> 
>  (tagbody
>    (let ((x 3))
>      (unwind-protect
>        (if (numberp x) (go out))
>        (print x)))
>   out
>    ...)
> 
> Then it says:
> 
>     When go is executed, the call to print is executed
>     first, and then the transfer of control to the tag
>     out is completed.

OK.  This concerns the order of transfer of control and the execution of
the clean-up forms.

> Now compare the above with the following example:
> 
>  (defun dummy-function (x)
>     (setq state 'running)
>     (unless (numberp x) (throw 'abort 'not-a-number))
>     (setq state (1+ x))) =>  DUMMY-FUNCTION
>  (catch 'abort (dummy-function 1)) =>  2
>  state =>  2
>  (catch 'abort (dummy-function 'trash)) =>  NOT-A-NUMBER
>  state =>  RUNNING
>  (catch 'abort (unwind-protect (dummy-function 'trash)
>                   (setq state 'aborted))) =>  NOT-A-NUMBER
>  state =>  ABORTED
> 
> 
> Based on the behaviour of the 2nd example, it looks as
> if dummy-function is called first and then the cleanup
> form (setq state 'aborted) is executed.

Yes.  What else can UNWIND-PROTECT do?  It has to execute DUMMY-FUNCTION
first.  It can't do the cleanup forms until after it tries executing the
main form.  It is just that any transfer of control that emanates from
the main form (via the THROW inside DUMMY-FUNCTION) is handled after the
clean-up forms execute.  Otherwise they wouldn't really be clean-up
forms.

This doesn't seem inconsistent to me.  Just think about what would make
sense in this case.

>  But in the 1st
> example the cleanup form (print x) is executed first
> and then the transfer of control happens. So why is the
> behaviour different in the 2 cases ? Is it because the
> compiler can see that after calling dummy-function the
> flow of execution will return inside the unwind-protect
> form so the call to dummy-function does not count as an
> exit ?

The call to dummy-function is not an exit.  It is the call to throw
inside dummy-function that is the exit.  And that throw is interrupted
so that the clean-up forms can run.  That is the entire point of using
unwind-protect.

> 2nd question:
> 
> A bit further down we see the following example:
> 
>     The following code is not correct:
> 
>  (unwind-protect
>    (progn (incf *access-count*)
>           (perform-access))
>    (decf *access-count*))
> 
>     If an exit occurs before completion of incf, the
>     decf form is executed anyway, resulting in an
>     incorrect value for *access-count*. The correct
>     way to code this is as follows:
> 
>  (let ((old-count *access-count*))
>    (unwind-protect
>      (progn (incf *access-count*)
>             (perform-access))
>      (setq *access-count* old-count)))
> 
> How can an exit occur before completion of incf ?

Well, perhaps a number of ways.  Maybe the user types Control-C?

> And in the "correct" code what happens if there is
> no exit ? (setq *access-count* old-count) will be
> executed and *access-count* is still going to have
> the wrong value. I think I may be misunderstanding
> what the whole set-up is supposed to be or I'm
> missing some unstated assumptions about
> perform-access

I think you have uncovered a bug in the example.  Without having some
sort of WITHOUT-INTERRUPTS code around parts of this, I don't think you
can actually code it properly.  The crux of the issue is that you have
to have some method of either updating the count or setting a flag to
disable the count rollback following successful completion of the
PERFORM-ACCESS and the execution of the clean-up code.

You can't really do this without some sort of construct for atomic or
uninterruptable operations.

> 3rd question:
> 
> Again from the HS page:
> 
>     ;;; The following has undefined consequences.
>      (block a
>        (block b
>          (unwind-protect (return-from a 1)
>            (return-from b 2))))
> 
> Why is it undefined ? I can see it doesn't make
> sense but what rules does it violate ?

It violates the rule about the order of transfer of control.  After the
clean-up forms are executed, the transfer of control from inside the
UNWIND-PROTECT is supposed to happen.  But if the cleanup forms
themselves transfer control, you end up having one transfer of control,
followed by a different transfer of control.  What happens then is
really undefined.

It would be even worse if the nesting order of the blocks were reversed,
since once control transfered to "b", the label for "a" would not even
be visible.


-- 
Thomas A. Russ,  USC/Information Sciences Institute